I'm making a 2D Tower Defense game and want my towers to launch a prefab at minions. However it currently only spawns my desired prefab, but doesn't move it.
My two scripts:
public class Attacker : MonoBehaviour {
// Public variables
public GameObject ammoPrefab;
public float reloadTime;
public float projectileSpeed;
// Private variables
private Transform target;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
void OnTriggerEnter(Collider co){
if (co.gameObject.tag == "Enemy" || co.gameObject.tag == "BlockTower") {
Debug.Log("Enemy tag detected");
if(this.gameObject.tag == "Enemy" && co.gameObject.tag != "Enemy"){
Debug.Log("This is an Enemy");
// Insert for Enemey to attack Block Towers.
}
if(this.gameObject.tag == "Tower" && co.gameObject.tag != "BlockTower"){
Debug.Log("This is a Tower");
Tower Tower = GetComponent<Tower>();
Tower.CalculateCombatTime(reloadTime, projectileSpeed);
Transform SendThis = co.transform;
Tower.SetTarget(SendThis);
}
}
}
}
and
public class Tower : MonoBehaviour {
private Transform target;
private float fireSpeed;
private double nextFireTime;
private GameObject bullet;
private Attacker source;
// Use this for initialization
public virtual void Start () {
source = this.GetComponent<Attacker> ();
}
// Update is called once per frame
public virtual void Update () {
if (target) {
Debug.Log("I have a target");
//if(nextFireTime <= Time.deltaTime)
FireProjectile ();
}
}
public void CalculateCombatTime(float time, float speed){
Debug.Log("Calculate Combat Speed");
nextFireTime = Time.time + (time * .5);
fireSpeed = speed;
}
public void SetTarget(Transform position){
Debug.Log("Set Target");
target = position;
}
public void FireProjectile(){
Debug.Log("Shoot Projectile");
bullet = (GameObject)Instantiate (source.ammoPrefab, transform.position, source.ammoPrefab.transform.rotation);
float speed = fireSpeed * Time.deltaTime;
bullet.transform.position = Vector3.MoveTowards (bullet.transform.position, target.position, speed);
}
}
Basicly Attacker detects the object that collides with it, then if its tag is Tower it will send the information to Tower. My debug shows that every function works, even "Debug.Log("Shoot Projectile");" shows up.
However it doesn't move towards my target so I guess "bullet.transform.position = Vector3.MoveTowards (bullet.transform.position, target.position, step);" is never being executed?
Vector3.MoveTowards only moves the object once, it's just a instant displacement when the FireProjectile is called.
You need to create some kind of projectile script with an Update() function to make it move over time.
Here is an example:
public class Projectile : MonoBehaviour
{
public Vector3 TargetPosition;
void Update()
{
transform.position = Vector3.MoveTowards(transform.position, TargetPosition, speed * Time.DeltaTime);
}
}
Then right after your bullet instantiation, set the target:
bullet.GetComponent<Projectile>().TargetPosition = target.position;
Hope it helps.
You have to update the position of the bullet. You are only moving when you create the bullet.
Try to make a list of bullets and use the update function to change the position.
Related
my bird is colliding with the clouds but it only moves them and doesn't trigger it
my character
public float jumpForce = 5f;
public float gravity = -9.81f;
public GameObject gus;
public Transform rotation_checker;
public Transform chekced;
float velocity;
void Start()
{
}
// Update is called once per frame
void Update()
{
gameObject.transform.eulerAngles = new Vector2(0,0);
velocity += gravity * Time.deltaTime;
if (Input.GetKeyDown(KeyCode.W))
{
velocity = jumpForce;
}
rotation_checker.position = chekced.position;
transform.Translate(new Vector2(0, velocity) * Time.deltaTime);
}
private void OnTriggerExit2D(Collider2D collider)
{
Debug.Log(collider.gameObject);
if(collider.gameObject.name == "skybluscene")
{
Destroy(gameObject);
}
}
private void onCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "cloud")
{
Destroy(gameObject);
}
}
the cloud
float x = -4f;
void Start()
{
}
// Update is called once per frame
void Update()
{
gameObject.transform.Translate(new Vector2(x, 0) * Time.deltaTime);
}
void OnTriggerExit2D(Collider2D collider)
{
if ( collider.gameObject.tag == "scene")
{
Destroy(gameObject);
}
}
cloud works just fine, it destroys itself when it leaves the scene but bird doesn't destroy itself when it collides with the cloud
both bird and cloud have dynamic rigidbody2d and a collider
First of all: On your character script, your onCollisionEnter2D is misspelled. It needs to start with a capital letter.
Second: all your other methods use tags to identify what GameObject they collided with, but "skybluscene" (which also looks like a typo) is identified by its gameObject name.
Third: I'm not sure, but I find it odd that you're using both triggers and collisions in the same script.
I'm doing a top-down shooter in unity, I'm trying to make the enemy shoot when it sees the player. So far, this is my code:
public class EnemyShooting : MonoBehaviour
{
public Transform firePoint;
public GameObject bulletPrefab;
public float bulletForce = 20f;
public FieldOfView _fieldofview;
void Start()
{
_fieldofview = FindObjectOfType<FieldOfView>();
}
// Update is called once per frame
void Update()
{
if(_fieldofview.canSeePlayer)
{
Shoot();
}
}
void Shoot()
{
GameObject bullet = Instantiate(bulletPrefab, firePoint.position, firePoint.rotation);
Rigidbody2D rb2d = bullet.GetComponent<Rigidbody2D>();
rb2d.AddForce(firePoint.up * bulletForce, ForceMode2D.Impulse);
}
}
However, when the player is detected it just spams the bullets due it doesn't has a cool down timer. I've tried with a Coroutine and the Invoke method but it doesn't works. Any ideas?
The following code defines two times. The current cooldown and shoot cooldown. Add the bottom and your problem will be solved.
public float shootCooldown = 5; // 5sec for e.g.
private float currentCooldown;
void Update()
{
if (currentCooldown > 0) // If shoot not in cooldown
{
currentCooldown = shootCooldown; // Set current cooldown time to shootCooldown
if(_fieldofview.canSeePlayer) Shoot();
}
else currentCooldown -= Time.deltaTime; // Reduce cooldown over time
}
I am new at game dev, and I have question, I have my enemies prefabs, and enemy script, contains
public Transform player;
So Instead of every time putting my player into that 'slot', I want to make, script will be finding my player, I tried
private Transform player = GameObject.Find("player")
but it shows error
Here is the full script
public class Enemies : MonoBehaviour
{
public Transform player = GameObject.Find("Player");
private Rigidbody2D rb;
private Vector2 movement;
public float speed = 5f;
public int health;
// Start is called before the first frame update
void Start()
{
rb = this.GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
Vector2 direction = player.position - transform.position;
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
rb.rotation = angle;
direction.Normalize();
movement = direction;
}
private void FixedUpdate()
{
Move(movement);
}
void Move(Vector2 direction)
{
rb.MovePosition((Vector2)transform.position + (direction * speed * Time.deltaTime));
}
private void OnMouseDown()
{
health = health - 1;
if(health <= 0)
{
Destroy(gameObject);
}
}
}
First of all you can't use Find in a static context. (Which is probably the error you are referring to.)
It goes a bit deeper into how c# works but in simple words: The class fields are all initialized even before the constructor is executed and thus at a moment when there still is no instance.
Secondly: GameObject.Find returns a GameObject not a Transform.
So if anything it would probably rather be
// Best would still be to drag this in if possible
[SerializeField] private Transform player;
void Start()
{
if(!player) player = GameObject.Find("Player").transform;
rb = this.GetComponent<Rigidbody2D>();
}
In general I always recommend to not use Find at all if anyhow possible. It is basically just a more expensive way of using some static or manager/provider based code
Why is this connecting all the health bars of my enemies together, even though their actual health is decreasing at its specified rate?
public class FillHealth : MonoBehaviour
{
Image HealthBar;
private NormalMonster normalMonster;
// Start is called before the first frame update
void Start()
{
HealthBar = GetComponent<Image>();
normalMonster = GameObject.FindGameObjectWithTag("Normal Monster").GetComponent<NormalMonster>();
}
// Update is called once per frame
void Update()
{
UpdateHealthLeft();
}
public void UpdateHealthLeft()
{
if (normalMonster.healthLeft > 0)
{
HealthBar.fillAmount = normalMonster.healthLeft / normalMonster.setHealth;
}
}
}
This is the script that is being referenced in FillHealth. As far as I understand it, since the variable isn't static, then the values should not be shared. It should find fill the health bar for each individual enemy.
public class NormalMonster : MonoBehaviour
{
private float _normalSpeed = 2f;
private float _BaseHealth = 20f;
private float _HealthModifier;
public float setHealth;
public float healthLeft;
// Start is called before the first frame update
void Start()
{
UpdateHealth();
}
// Update is called once per frame
void Update()
{
NormMonMov();
}
public void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Arrows")
{
healthLeft -= Arrows.Damage;
Destroy(other.gameObject);
if (healthLeft <= 0f)
{
Destroy(this.gameObject);
EarnedGold.earnedGold += 7;
Spawn_Manager.enemyCount--;
}
}
}
public void UpdateHealth()
{
if (StageMode.StageLvl > 5)
{
_HealthModifier = (StageMode.StageLvl * 0.01f) * _BaseHealth;
setHealth = Mathf.Round(_BaseHealth + _HealthModifier);
}
else
{
setHealth = _BaseHealth;
}
healthLeft = setHealth;
}
public void NormMonMov()
{
transform.Translate(Vector3.left * _normalSpeed * Time.deltaTime);
transform.position = new Vector3(Mathf.Clamp(transform.position.x, -7.0f, 10), transform.position.y, 0);
}
}
Any help would be greatly appreciated for this guy who just start playing with unity this weekend.
I believe the issue is with normalMonster = GameObject.FindGameObjectWithTag("Normal Monster").GetComponent<NormalMonster>();
If you have two Monsters
Both have two scripts attached, FillHealth and NormalMonster
Both the "FillHealth" scripts look for the FIRST gameobject in the scene that has a script with tag NormalMonster so both monsters are pointing to the exact same NormalMonster script (the first in the list)
Change "GameObject" capital G to "gameObject" lower case g
Still not the best way to code this, but that may work I think
Instead of getting the image, get the rect transform, like this
public RectTransform healthBar;
and change the length with:
healthBar.sizeDelta = new Vector2(normalMonster.healthLeft,healthBar.sizeDelta.y);
I'm creating a basic AI script for my enemies in Unity and I have most of it working the way I want it to. The way I have my enemies set up they contain 2 colliders, a polygon collider that destroys the player when touched, and an empty game object that's a child of the enemy that is a circle collider that acts as a trigger. There's a game object that's tagged Straight Road and when the circle collider comes in contact with it, it should run a function called StopMovement(); that sets the enemies movement to 0. I used to Debug.Log(); to check to see if the collider recognizes that it's touching Straight Road and it doesn't. This is my code below. I'm hoping someone has a suggestion.
public class DogAI : GenericController {
public Transform target;
public float chaseRange;
public float maxDistance;
private Vector3 targetDirection;
private float targetDistance;
// Use this for initialization
void Start()
{
base.Start();
}
// Update is called once per frame
void Update()
{
base.Update();
if (target.transform != null)
{
targetDirection = target.transform.position - transform.position;
targetDirection = targetDirection.normalized;
targetDistance = Vector3.Distance(target.position, transform.position);
if (targetDistance <= chaseRange)
{
SetMovement(targetDirection);
}
Vector3 enemyScreenPosition = Camera.main.WorldToScreenPoint(transform.position);
if (targetDistance > maxDistance)
{
Destroy(gameObject);
}
}
}
void StopMovement()
{
SetMovement(new Vector2(0,0));
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("Straight Road"))
{
Debug.Log("Stop! There's a road!");//This never shows up in the log?
StopMovement();
}
}
void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.CompareTag("Player"))
{
DestroyObject(other.gameObject);
}
}
Generic Controller script below containing the SetMovement() function.
public abstract class GenericController : MonoBehaviour
{
public float movementSpeed = 20;
float animationSpeed = 1;
protected Rigidbody2D rigidbody;
protected Animator animator;
Vector2 movementVector;
float currentSpeed;
protected bool needAnimator = true;
// Use this for initialization
protected void Start()
{
rigidbody = GetComponent<Rigidbody2D>();
if (needAnimator)
{
animator = GetComponent<Animator>();
animator.speed = animationSpeed;
}
}
protected void FixedUpdate()
{
rigidbody.velocity = movementVector;
currentSpeed = rigidbody.velocity.magnitude;
if (needAnimator)
animator.SetFloat("Speed", currentSpeed);
}
public void SetMovement(Vector2 input)
{
movementVector = input * movementSpeed;
}
public void SetMovement(int x, int y)
{
SetMovement(new Vector2(x, y));
}
From the documentation:
MonoBehaviour.OnTriggerEnter2D(Collider2D)
Sent when another object enters a trigger collider attached to this object (2D physics only).
The keyword here is enters. In other words, a trigger is for when something goes inside the area of the collider, like a player entering a region of the map, where the region is a trigger collider. If you want something to happen when a collider collides with the road, i.e. when your CircleCollider comes in contact with the road, then you want the collider to not be a trigger, and you want the functionality to be inside OnCollisionEnter2D.