Here is a Video also - http://tinypic.com/r/mmagki/9
Here is my start () function
void Start()
{
target = GameObject.FindGameObjectWithTag("Player").transform;
}
and update() function
void Update()
{
transform.LookAt(target);
float step = speed * Time.deltaTime;
distance = (transform.position - target.position).magnitude;
//Debug.Log("Now distance -" + distance);
if (distance < 20)
{
// print("In Range");
transform.GetComponent<Animation>().Play("attack", PlayMode.StopAll);
if (isAttacking == false)
{
isAttacking = true;
Hit.playerHealth -= Random.Range(20f, 25f) * Time.deltaTime;
Hit.playerHealth -= Random.Range(20f, 25f);
// StartCoroutine(MyCoroutine(4));
// print("Player Health Status = " + Hit.playerHealth);
if (Hit.playerHealth <= 0)
{
// print("Player dead");
}
}
else
{
}
}
else
{
// print("Out of Range");
transform.position = Vector3.MoveTowards(transform.position, target.position, step);
transform.GetComponent<Animation>().Play("walk", PlayMode.StopAll);
}
}
My Zombie (Enemy) is approaching Player, when Zombie gets hit with the wall, he should go to gate.
What i did, as soon as Zombie (Set Trigger = checked) hit with the wall, i have changed the reference of 'target' to Object with tag 'gate'.
Now the Zombie is not moving towards gate object (i have set tag 'gate' also). he is still moving towards player only. Not able to change the reference of target.
void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "wall")
{
target = GameObject.FindGameObjectWithTag("gate").transform;
Debug.Log("Yes its a onTrigger Enter function , hitting with wall");
}
}
It looks like you don't have a collider on your gate. Adding a collider to the gate should do the trick.
first verify the tags of all are seted correctly, then you need to add a rigidbody component at least in one of the objects as Unity Docs say: OnTriggerEnter . I recomend to add it to the zombie and set UseGravity to false. and with this it should work... sorry for bad english.
Related
I am very new to c# and I've come across a problem with my enemy spawner. I am making an endless top-down shooter, and after 20 enemies have been spawned and then destroyed, the enemies stop appearing. My score counter continues to increase though, which leads me to believe they're somehow being destroyed.
Here is the enemy spawn controller:
void Update()
{
if (!spawningObject && GameController.EnemyCount < spawnSettings[0].maxObjects)
{
spawningObject = true;
float pick = Random.value * totalWeight;
int chosenIndex = 0;
float cumulativeWeight = enemySpawnables[0].weight;
while(pick > cumulativeWeight && chosenIndex < enemySpawnables.Count - 1)
{
chosenIndex++;
cumulativeWeight += enemySpawnables[chosenIndex].weight;
}
StartCoroutine(SpawnObject(enemySpawnables[chosenIndex].type, Random.Range(spawnSettings[0].minWait / GameController.DifficultyMultiplier, spawnSettings[0].maxWait / GameController.DifficultyMultiplier)));
Spawn();
}
}
private IEnumerator SpawnObject(string type, float time)
{
yield return new WaitForSeconds(time);
randomSpawnPoint = Random.Range(0, enemySpawners.Length);
randomEnemy = Random.Range(0, enemy.Length);
enemyPool.SpawnObject(enemySpawners[randomSpawnPoint].position, transform.rotation);
spawningObject = false;
GameController.EnemyCount++;
}
And here is the enemy controller (attached to enemy prefab):
public void Start()
{
myRB = GetComponent<Rigidbody>();
player = FindObjectOfType<PlayerController>();
}
private void OnEnable()
{
player = FindObjectOfType<PlayerController>();
}
void Update()
{
if (health <= 0)
{
Die();
}
}
void FixedUpdate()
{
transform.LookAt(player.transform.position);
myRB.velocity = (transform.forward * moveSpeed);
//Falling
if(myRB.velocity.y < 0)
{
myRB.velocity += Vector3.up * Physics.gravity.y * (fallMultiplier - 1) * Time.deltaTime;
}
}
public void Die()
{
print("Enemy" + this.gameObject.name + " has died!");
EnemySpawn.instance.enemyPool.ReturnObject(this.gameObject);
player.GetComponent<PlayerController>().points += pointsToGive;
ScoreController.scoreValue += 1;
}
The only thing that should affect enemy health is the bullet. Here is the part of the bullet controller that affects enemy health:
public void OnTriggerEnter(Collider other)
{
if(other.tag == "Enemy")
{
triggeringEnemy = other.gameObject;
triggeringEnemy.GetComponent<EnemyController>().health -= damage;
PlayerController.instance.bulletPool.ReturnObject(gameObject);
}
}
The issue always happens after 20 enemies have been spawned/destroyed and 20 points have been achieved. Before then, enemy movement is normal and everything seems to be working as it should. It could very well be something very simple that I'm missing, but I'd appreciate any help!
When you do engage logic mistake good way to find it is Visual Studio debugging.
Here is good official article about whole process - https://docs.unity3d.com/Manual/ManagedCodeDebugging.html.
I would place breakpoint right at start of Update method to see that EnemyCount increases over time and then you will understand that you forgot to do decrement it.
I am developing an infinite tower jumping game using Unity2D, and currently working on a continually moving object which causes the player to die if contact is made. The player can also die if they either fall off of a platform or off-screen. All methods of death rely on a BoxCollider2D I am using as a Killbox (tagged accordingly) - the player sprite has a RigidBody2D and BoxCollider2D attached to it - so there is one attached to the main camera (it moves as the player sprite progresses through the level) and to the top of the moving object.
The current code I have works up to the point where the game over screen appears on player death, but the object continues to move whilst everything else stops.
Here is my code for the moving object:
public class Water : MonoBehaviour {
private Collider2D playerCollider;
public ControllerNew thePlayer;
private float speed = 2f;
public GameManager theGameManager;
//reference scoremanager
private ScoreManager theScoreManager;
bool shouldMove = true;
// Start is called before the first frame update
void Start()
{
theScoreManager = FindObjectOfType<ScoreManager>();
thePlayer = FindObjectOfType<ControllerNew>();
lastPlayerPosition = thePlayer.transform.position;
}
// Update is called once per frame
void Update()
{
if (shouldMove = true)
{
transform.position += Vector3.up * speed * Time.deltaTime;
if (theScoreManager.scoreCount > 100 && shouldMove)
{
transform.position += Vector3.up * (speed+1) * Time.deltaTime;
}
if (theScoreManager.scoreCount > 250 && shouldMove)
{
transform.position += Vector3.up * (speed +2) * Time.deltaTime;
}
if (theScoreManager.scoreCount > 500 && shouldMove)
{
transform.position += Vector3.up * (speed+4) * Time.deltaTime;
}
if (theScoreManager.scoreCount > 1000 && theScoreManager.scoreCount > theScoreManager.hiScoreCount && shouldMove)
{
transform.position += Vector3.up * (speed+5) * Time.deltaTime;
}
}
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.tag == "Player New")
{
transform.position += Vector3.zero * (speed * 0) * Time.deltaTime;
}
//call object by its tag
if (other.gameObject.tag == "Killbox")
{
shouldMove = false;
if (shouldMove = false){
theGameManager.RestartGame();
transform.position += Vector3.zero * (speed * 0) * Time.deltaTime;
}
}
}
Edit (Issue resolved)
So it turns out that after adding the Water.StopMoving() method into my controller script, the water had not been called as a GameObject in void Start(). Once this was added, the water object stopped on collision.
Just want to say thank you #D.B for your help and bearing with me - apologies if the info I gave wasn't everything you needed to be able to assist me
You made a mistake on the first line of the Update() method :
if (shouldMove = true)
You set the bool to true, not comparing it. Use double = otherwise it will set the bool to true at every frame.
if (shouldMove == true)
By the way you can simplify this part :
//call object by its tag
if (other.gameObject.tag == "Killbox")
{
shouldMove = false;
theGameManager.RestartGame();
transform.position += Vector3.zero * (speed * 0) * Time.deltaTime;
}
(You forgot a = too)
I made a test with this simplified script
void Update()
{
if (shouldMove == true)
{
Debug.Log("move");
transform.position += Vector3.up * speed * Time.deltaTime * GetDifficultyFactor();
}
}
private float GetDifficultyFactor()
{
float factor = 1f;
if(theScoreManager.scoreCount > 100)
{
factor += 1f;
}
if (theScoreManager.scoreCount > 250)
{
factor += 2f;
}
// Add all your speed modification condition here
return factor;
}
void OnTriggerEnter2D(Collider2D other)
{
Debug.Log("trigger");
//call object by its tag
if (other.gameObject.tag == "Killbox")
{
Debug.Log("die");
shouldMove = false;
}
}
And it work fine. Are you sure you have a collider2D set to trigger on your charactert with the tag "Killbox" (with the first letter in uppercase ?). You should have a rigidbody2d on the character too.
Mistake come frome another part of your code or with some trouble with collider2D/tag/RigidBody2D. Without seeing all it's difficult to help you more.
You should try to add some Debug.Log() or use debugeur with breakpoint to be sure code enter into your "die" statement and then not going on the Update if statement. If yes, it's mean you probably set the shouldMove variable in another part of your script.
Answer regarding discussion in comments
I think you want to make this OnTriggerEnter2D logic in both script.
Without seeing all your project I suggest you to make a reference bewteen your character and your water script. Then when the player die the player script will call a method on water to stop it.
public class Player : MonoBehaviour
{
public Water Water;
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.tag == "Killbox")
{
Debug.Log("die");
Water.StopMoving();
}
}
}
public class Water : MonoBehaviour
{
private bool shouldMove;
public void Update()
{
...
}
public void StopMoving()
{
shouldMove = false;
}
// No Trigger logic here
}
I'm coding my games boss behaviour and in the final stage of the battle the boss is supposed to charge towards the player and then move back to its original position. Wait for 5 seconds and then do the same.
I tried to achieve this using coroutines and Vector2.MoveTowards() but am not getting the desired effect, first off the boss does not "Move Towards" the player but instantly appears at the targetPosition and then just stays there, does not move back.
Below is my code:
private Vector2 chargeTarget;
private Vector2 tankStartPosition;
void Start()
{
chargeTarget = new Vector2(-5.0f, transform.position.y);
tankStartPosition = transform.position;
}
void Update()
{
if (Time.time > nextCharge)
{
StartCoroutine(TankCharge());
nextCharge = Time.time + chargeRate;
}
}
IEnumerator TankCharge()
{
transform.position = Vector2.MoveTowards(tankStartPosition, chargeTarget, Time.deltaTime * chargeSpeed);
transform.position = Vector2.MoveTowards(chargeTarget, tankStartPosition, Time.deltaTime * returnSpeed);
}
Any idea what I am doing wrong here? And how to get my desired action?
Thank you
Calling MoveTowards once only moves the game object once during that iteration of the game loop. Calling MoveTowards once doesn't move the game object all the way to its target (unless the maxDistanceDelta parameter is big enough to move the game object to its target in one iteration).
If the boss is instantly appearing at the target, I'm guessing your chargeSpeed is too big.
What you want to do is call MoveTowards once per Update cycle. However, the way you're doing your coroutine, the coroutine will only move the game object once and then exit. Normally coroutines will have a loop within them (otherwise the coroutine will exit after running once). Something like this:
IEnumerator TankCharge()
{
while (Vector3.Distance(transform.position, chargeTarget.position) > Mathf.Epsilon)
{
// Adjust this so this game object doesn't move the entire
// distance in one iteration
float distanceToMove = Time.deltaTime * chargeSpeed;
transform.position = Vector3.MoveTowards(transform.position, chargeTarget.position, distanceToMove)
yield return null;
}
}
However, for your situation, you don't really need a coroutine. You can just do this directly in Update()
private bool returnToStart = false;
private float timer;
void Update
{
float distanceToMove = Time.deltaTime * chargeSpeed;
if (timer <= 0)
{
if (!returnToStart)
{
transform.position = Vector3.MoveTowards(transform.position, chargeTarget.position, distanceToMove)
// Target reached? If so, start moving back to the original position
if (Vector3.Distance(transform.position, chargeTarget.position) <= Mathf.Epsilon)
{
returnToStart = true;
this.timer = this.chargeRate;
}
}
else
{
transform.position = Vector3.MoveTowards(transform.position, tankStartPosition.position, distanceToMove)
// Original position reached? If so, start moving to the target
if (Vector3.Distance(transform.position, tankStartPosition.position) <= Mathf.Epsilon)
{
returnToStart = false;
this.timer = this.chargeRate;
}
}
}
else
{
this.timer -= Time.time;
}
}
I make effect for my enemy if the player is near to the enemy decrease the player speed its work fine but if there's one of the same enemies in the filed has the same script or work if there's more all of them are near to the player not work if there's more then one of the same enemy one near to the the player and the other not here's the question
how to make enemy does the effect if there's more then one of the same enemy that has the same effect and only one near the player or more ?
here is my effect script
void Update () {
if (Vector3.Distance (target.position, transform.position) < 20) {
// if the enemy near effect
player.speed = 5f;
} else {
player.speed = 10f;
}
Here is a solution that doesn't require you to add or change the location of scripts and/or add any more components to the object.
By using a short buffer of time and only removing the slow if the "enemy" was previously slowing, you can do without the extra steps.
This is not the BEST way of doing this, some great suggestions in the comments, but it is A way of doing this.
Tested and confirmed to work in Unity.
private float timeUntilSlowEnds = 0;
private bool isSlowing = false;
void Update ()
{
if (Vector3.Distance (target.position, transform.position) < 20)
{
//1/4 second of slow time
timeUntilSlowEnds = 0.25f;
isSlowing = true;
}
if(timeUntilSlowEnds <= 0)
{
if(isSlowing)
{
//only reset player speed if ending slow
isSlowing = false;
player.speed = 10.0f;
}
}
else
{
//slow the player each frame and decrease the timer
player.speed = 5.0f;
timeUntilSlowEnds -= Time.deltaTime;
}
}
I have been trying to make this work for a while and I am failing.
I have a Rigidbody2D in a top down 2D level and I am trying to simply move it along the coordinates (the levels are gridlike) so one step / button press equals exactly one square walked. It should only be possible to walk in one of the four directions, and no matter when the user stops the walking motion, it should end on a square. A good game equivalent of what I want to achieve is the lufia / breath of fire / any similar RPG series. I've tried using coroutines to get the update function to wait one second after every step, but that does not seem to work. The closest I've come to is the code down below. Thanks guys!
public class PlayerMovement2D : MonoBehaviour
{
Rigidbody2D rbody;
Animator anim;
float speed = 1.25f;
Vector2 pos;
void Start()
{
rbody = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
pos = rbody.position;
}
void FixedUpdate()
{
Vector2 movement_vector = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
if (movement_vector.x != 0 || movement_vector.y != 0)
{
if (movement_vector.x > 0)
{
pos += Vector2.right;
pos.y = 0;
}
else if (movement_vector.x < 0)
{
pos += Vector2.left;
pos.y = 0;
}
else if (movement_vector.y > 0)
{
pos += Vector2.up;
pos.x = 0;
}
else if (movement_vector.y < 0)
{
pos += Vector2.down;
pos.x = 0;
}
anim.SetBool("IsWalking", true);
anim.SetFloat("Input_x", movement_vector.x);
anim.SetFloat("Input_y", movement_vector.y);
}
else
{
anim.SetBool("IsWalking", false);
}
rbody.position = Vector2.MoveTowards(rbody.position, pos, speed * Time.deltaTime);
//transform.position = Vector3.MoveTowards(transform.position, pos, Time.deltaTime * speed);
pos = rbody.position;
}
}
I think what you try to program is similar to the RogueLike game that is part of the tutorial collection in Unity website. Check first the intro video to confirm that is what you are planning to achieve
https://unity3d.com/es/learn/tutorials/projects/2d-roguelike-tutorial/project-introduction?playlist=17150
If so, this is how they handle it:
using UnityEngine;
using System.Collections;
//The abstract keyword enables you to create classes and class members that are incomplete and must be implemented in a derived class.
public abstract class MovingObject : MonoBehaviour
{
public float moveTime = 0.1f; //Time it will take object to move, in seconds.
public LayerMask blockingLayer; //Layer on which collision will be checked.
private BoxCollider2D boxCollider; //The BoxCollider2D component attached to this object.
private Rigidbody2D rb2D; //The Rigidbody2D component attached to this object.
private float inverseMoveTime; //Used to make movement more efficient.
//Protected, virtual functions can be overridden by inheriting classes.
protected virtual void Start ()
{
//Get a component reference to this object's BoxCollider2D
boxCollider = GetComponent <BoxCollider2D> ();
//Get a component reference to this object's Rigidbody2D
rb2D = GetComponent <Rigidbody2D> ();
//By storing the reciprocal of the move time we can use it by multiplying instead of dividing, this is more efficient.
inverseMoveTime = 1f / moveTime;
}
//Move returns true if it is able to move and false if not.
//Move takes parameters for x direction, y direction and a RaycastHit2D to check collision.
protected bool Move (int xDir, int yDir, out RaycastHit2D hit)
{
//Store start position to move from, based on objects current transform position.
Vector2 start = transform.position;
// Calculate end position based on the direction parameters passed in when calling Move.
Vector2 end = start + new Vector2 (xDir, yDir);
//Disable the boxCollider so that linecast doesn't hit this object's own collider.
boxCollider.enabled = false;
//Cast a line from start point to end point checking collision on blockingLayer.
hit = Physics2D.Linecast (start, end, blockingLayer);
//Re-enable boxCollider after linecast
boxCollider.enabled = true;
//Check if anything was hit
if(hit.transform == null)
{
//If nothing was hit, start SmoothMovement co-routine passing in the Vector2 end as destination
StartCoroutine (SmoothMovement (end));
//Return true to say that Move was successful
return true;
}
//If something was hit, return false, Move was unsuccesful.
return false;
}
//Co-routine for moving units from one space to next, takes a parameter end to specify where to move to.
protected IEnumerator SmoothMovement (Vector3 end)
{
//Calculate the remaining distance to move based on the square magnitude of the difference between current position and end parameter.
//Square magnitude is used instead of magnitude because it's computationally cheaper.
float sqrRemainingDistance = (transform.position - end).sqrMagnitude;
//While that distance is greater than a very small amount (Epsilon, almost zero):
while(sqrRemainingDistance > float.Epsilon)
{
//Find a new position proportionally closer to the end, based on the moveTime
Vector3 newPostion = Vector3.MoveTowards(rb2D.position, end, inverseMoveTime * Time.deltaTime);
//Call MovePosition on attached Rigidbody2D and move it to the calculated position.
rb2D.MovePosition (newPostion);
//Recalculate the remaining distance after moving.
sqrRemainingDistance = (transform.position - end).sqrMagnitude;
//Return and loop until sqrRemainingDistance is close enough to zero to end the function
yield return null;
}
}
//The virtual keyword means AttemptMove can be overridden by inheriting classes using the override keyword.
//AttemptMove takes a generic parameter T to specify the type of component we expect our unit to interact with if blocked (Player for Enemies, Wall for Player).
protected virtual void AttemptMove <T> (int xDir, int yDir)
where T : Component
{
//Hit will store whatever our linecast hits when Move is called.
RaycastHit2D hit;
//Set canMove to true if Move was successful, false if failed.
bool canMove = Move (xDir, yDir, out hit);
//Check if nothing was hit by linecast
if(hit.transform == null)
//If nothing was hit, return and don't execute further code.
return;
//Get a component reference to the component of type T attached to the object that was hit
T hitComponent = hit.transform.GetComponent <T> ();
//If canMove is false and hitComponent is not equal to null, meaning MovingObject is blocked and has hit something it can interact with.
if(!canMove && hitComponent != null)
//Call the OnCantMove function and pass it hitComponent as a parameter.
OnCantMove (hitComponent);
}
//The abstract modifier indicates that the thing being modified has a missing or incomplete implementation.
//OnCantMove will be overriden by functions in the inheriting classes.
protected abstract void OnCantMove <T> (T component)
where T : Component;
}
Link to this part of the tutorial:
https://unity3d.com/es/learn/tutorials/projects/2d-roguelike-tutorial/moving-object-script?playlist=17150
You can use Vector2.Lerp method in combination of Unity's Coroutine system.
public class Movement
: MonoBehaviour
{
IEnumerator m_MoveCoroutine;
float m_SpeedFactor;
void Update()
{
// if character is already moving, just return
if ( m_MoveCoroutine != null )
return;
// placeholder for the direction
Vector2 direction; // between { -1, -1 } and { 1, 1 }
// calculate your direction based on the input
// and set that direction to the direction variable
// eg. direction = new Vector2(Input.GetAxisRaw("Horizontal") > 0 ? 1 : -1,...)
// then check if direction is not { 0, 0 }
if( direction != Vector2.zero )
{
// start moving your character
m_MoveCoroutine = Move(direction);
StartCoroutine(m_MoveCoroutine);
}
}
IEnumerator Move(Vector2 direction)
{
// Lerp(Vector2 a, Vector2 b, float t);
Vector2 orgPos = transform.Position; // original position
Vector2 newPos = orgPos + direction; // new position after move is done
float t = 0; // placeholder to check if we're on the right spot
while( t < 1.0f ) // loop while player is not in the right spot
{
// calculate and set new position based on the deltaTime's value
transform.position = Vector2.Lerp(orgPos, newPos, (t += Time.deltaTime * m_SpeedFactor));
// wait for new frame
yield return new WaitForEndFrame();
}
// stop coroutine
StopCoroutine(m_MoveCoroutine);
// get rid of the reference to enable further movements
m_MoveCoroutine = null;
}
}
This method assumes that you can move in the specified direction. But you still should check if your new position is "walkable" before starting the Move Coroutine.
I think you want to do the movement in a coroutine that, while it is active, disabled further movement until it's done.
bool isIdle = true;
void Update() {
if(isIdle) {
// Your movement code that gives pos
StartCoroutine(Move(pos));
}
}
IEnumerator Move(Vector2 destination) {
isIdle = false;
do {
transform.position = Vector2.MoveTowards(transform.position, destination, speed * Time.deltaTime);
yield return new WaitForEndOfFrame();
} while (transform.position != destination);
isIdle = true;
}
Let me know if you don't understand, need further clarification or if this approach doesn't work!