Activating animation when within radius - c#

I am trying to create a script for my enemy turret, but it is not going well. I have a couple animations of the turret being activated and deactivated. What I need is that based on the distance from the player, it plays either animation. So once it moves inside the detection radius it plays the activation animation and once it is outside it plays the deactivation animation. Most of the other ways I try require me to create an Animation Controller, which I have little experience in using. I want a simple way to play one animation once it is inside and play a different one when it is outside. I think there was a way to store the animation clip in the script, and then play it. I have attached my current script, so you know what I mean.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyTurret : MonoBehaviour
{
public GameObject Player;
public float DistanceToPlayer;
public float DetectionRadius = 75;
// Start is called before the first frame update
void Start()
{
Player = GameObject.FindGameObjectWithTag("PlayerTank");
}
// Update is called once per frame
void Update()
{
DistanceToPlayer = Vector3.Distance(transform.position, Player.transform.position);
if (DistanceToPlayer<=DetectionRadius)
{
Debug.Log("Within Radius");
}
if (DistanceToPlayer >= DetectionRadius)
{
Debug.Log("Outside Radius");
}
}
}

Calculating and checking the distance of the player for every update() is not ideal. It will work, but it will do more work than it needs to when the player isn't even near it. Its not efficient.
What you may want to do if your player is a Rigidbody, is add a SphereCollider to the turret, set isTrigger=true, set the radius to be your detection radius, and handle the OnTriggerEnter() and OnTriggerExit() events to play or stop animations.
You can also add two public Animiation objects to the script, drag and drop your animations in the editor, then you can use animation.Play() and .Stop() etc. to control the animations.
Something similar to this. Not tested fully, but you can get the idea.
public float detectionRadius = 75;
public Animation activateAnimation;
public Animation deactivateAnimation;
void Start()
{
SphereCollider detectionSphere = gameObject.AddComponent<SphereCollider>();
detectionSphere.isTrigger = true;
detectionSphere.radius = detectionRadius;
detectionSphere.center = Vector3.zero;
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "PlayerTank")
{
activateAnimation.Play();
}
}
private void OnTriggerExit(Collider other)
{
if (other.gameObject.tag == "PlayerTank")
{
deactivateAnimation.Play();
}
}
Your animations must not loop otherwise you will have to add more logic to check if animation.isPlaying and do your own animation.Stop() etc.

Related

C# (Galaga Style Project) - Limit Projectile Prefab Clones based on Hierarchy

I am trying to practice creating a clone of Galaga following the same ruleset as the original. I am currently stuck trying to attempt a limit on the amount of cloned prefabs that can be in the scene at any one time, in the same way that Galaga's projectiles are limited to 2 on screen at any time. I want to make it so the player can shoot up to two projectiles, which destroy after 2 seconds or when they collide (this part is functioning), followed by not being able to shoot if two projectile clones are active and not yet destroyed in the hierarchy (Not working as I can instantiate projectiles over the limit of 2).
I have combed through Google for about 3 hours with no solutions that have worked for me, at least in the ways that I had attempted to implement them.
Thank y'all so much for the help!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class playerController : MonoBehaviour
{
public float moveSpeed = 1.0f;
public playerProjectile projectile;
public Transform launchOffset;
public int maxBullets = 0;
private GameObject cloneProjectile;
public Rigidbody2D player;
// Start is called before the first frame update
void Start()
{
player = this.GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
MovePlayer();
PlayerShoot();
}
public void MovePlayer()
{
player.velocity = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")) * moveSpeed;
}
public void PlayerShoot()
{
if (Input.GetKeyDown(KeyCode.Z))
{
var cloneProjectile = Instantiate(projectile, launchOffset.position, launchOffset.rotation);
maxBullets++;
if (maxBullets >= 3)
{
Destroy(cloneProjectile, 0.1f);
maxBullets --;
return;
}
}
}
}
You could change the logic up a bit. An instance of the playerController class is active as long as the game is active, so it will know and retain the value of 'maxBullets' until you die or exit the program.
So instead, every time you click "z", the first thing you should do is run the check. If the current amount of live projectiles equals the maximum, have the logic 'return' and exit out of the method.

UNITY PREFABS DOESN'T SHOW UP IN GAME TAB

As you can see in the screenshoot I can't see prefabs in the game tab but only in the editor. I have made a simple function for shooting(not finished yet), it works fine, it spawns the prefabs but i can't see them in the game tab, I have already tried changing the Sorting Layer, move the camera, change Z position but nothing appen.
This is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerAttack : MonoBehaviour
{
[SerializeField]
float delayBetweenShots = 0.4f;
float timePassedSinceLast = 0f;
// Start is called before the first frame update
void Start()
{
timePassedSinceLast = delayBetweenShots;
}
// Update is called once per frame
void Update()
{
Aiming();
Shooting();
}
void Aiming()
{
var objectPos = Camera.main.WorldToScreenPoint(transform.position);
var dir = Input.mousePosition - objectPos;
transform.rotation = Quaternion.Euler(new Vector3(0,0,Mathf.Atan2(-dir.x, dir.y) * Mathf.Rad2Deg));
}
void Shooting()
{
if(Input.GetMouseButton(0) && timePassedSinceLast >= delayBetweenShots)
{
GameObject bullet = (GameObject)Instantiate(Resources.Load("bullet"), transform.position, transform.rotation);
timePassedSinceLast = 0f;
}
else
{
timePassedSinceLast += Time.deltaTime;
}
}
}
The prefabs get instantiated correctly. As others suggested as well, the best way to find "lost" objects in your game is to shoot some stuff, pause the game, go into scene view, turn on 3D mode and double click one of the prefabs in the hierarchy. The camera will take you straight to your object.

Run code on every frame two colliders are in contact

im trying to make it so that if my character continues to touch a gameobject with a damage script the player continuesly gets damaged. instead of this result i only get damaged once when touching the gameobject. there are no error messages. ive tried to replace the if with a while loop and it ended up crashing my game. is there any way to loop a if statement preferably with a way to time it.
if (other.tag == "Player")
{
healthScript.healthPoints -= damage;
}
this is the if statement im trying to loop.
First, I am under the assumption you are using Colliders as triggers with the Is Trigger attribute selected. I am also assuming this is a 2D game. If not, this same approach will work, you will just have to change the methods from 2D to 3D.
You will want to add the OnTriggerEnter2D() and OnTriggerExit2D() methods to your player health script. These will allow us to detect when the player is standing on the damaging object. From here, we will start a coroutine which can be used to deal damage in a timed manner.
using System.Collections;
using UnityEngine;
public class HealthScript : MonoBehaviour
{
public float healthPoints = 100f;
public float damage = 5f;
public bool OnDamagingObject = false;
IEnumerator DealDamage()
{
while (OnDamagingObject)
{
healthPoints -= damage;
yield return new WaitForSeconds(1f);
}
}
void OnTriggerEnter2D(Collider2D hitInfo)
{
GameObject collider = hitInfo.gameObject;
if (collider.tag == "DamagingObject")
{
OnDamagingObject = true;
StartCoroutine(DealDamage());
}
}
void OnTriggerExit2D(Collider2D hitInfo)
{
GameObject collider = hitInfo.gameObject;
if (collider.tag == "DamagingObject")
{
OnDamagingObject = false;
}
}
}
For those who wish to use OnTriggerStay(), I will also provide a solution for that. This time, we will keep the script on the damaging object, following the structure the question asker is using.
using UnityEngine;
public class DamageScript : MonoBehaviour
{
public HealthScript healthScript;
public float damage = 5f;
void OnTriggerStay2D(Collider2D hitInfo)
{
GameObject other = hitInfo.gameObject;
if (other.CompareTag("Player"))
{
healthScript.healthPoints -= damage;
}
}
}
As mentioned in the comments of my other answer, implementing both OnTriggerEnter() and OnTriggerExit() allow more control over dealing damage to the player. One of the downsides to using OnTriggerStay() is I am unsure how to deal the damage in a timed manner. Additionally, OnTriggerStay() limits how you can deal damage to the player. If in the future you wanted to give 5 damage to the player when they first touch the damaging object but 2 damage for every second thereafter while touching it, it is not possible to do so using OnTriggerStay().

Understanding Unity's GameObject.Find(), GetComponent() and objects recycling

New to unity.
So I created a simple a simple muzzle flash particle animation that is supposed to be displayed on enemies gun when the player gets close to him, simulating a shot without the actual bullet. However I get a null reference exception in this part muzzleFlash.Play(); I believe it's because I am not actually getting the muzzle flash component in the start function with the code I have, actually I know that is it after going to in to debug mode I found out. I am having a really hard time figuring out how to access that component. Below is my code and I'm also posting a picture of my hierarchy. Thanks in advance.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class StaticShootingEnemy : MonoBehaviour
{
[SerializeField] private float _range = 12f;
private Transform _player;
private bool _alive;
private float _distance;
private ParticleSystem muzzleFlash;
// Use this for initialization
void Start()
{
_player = GameObject.Find("Player").transform;
_alive = true;
muzzleFlash = (ParticleSystem)this.gameObject.GetComponent("muzzleFLash");
}
// Update is called once per frame
void Update()
{
_distance = Vector3.Distance(this.transform.position, _player.transform.position);
if (_alive && _distance < _range)
AttackPlayer();
}
private void AttackPlayer()
{
//Turning enemy to look at player
transform.LookAt(_player);
Ray ray = new Ray(transform.position, transform.forward);
RaycastHit hit;
if (Physics.SphereCast(ray, 0.75f, out hit))
{
//TODO: Fix enemy shooting fast when gettting close to him.
GameObject hitObject = hit.transform.gameObject;
if (hitObject.GetComponent<PlayerController>())
{
muzzleFlash.Play();
Debug.Log("Player Hit!");
}
else
muzzleFlash.Stop();
}
}
public void SetAlive(bool alive)
{
_alive = alive;
}
}
You probably have an object "muzzleFlash" as child to object your script attached to. So, in this case you'd better have a reference to your ParticleSystem object that is called muzzleFlash.
[SerializeField] private ParticleSystem muzzleFlash; // drag and drop your ParticleSystem muzzleFlash in inspector
or at least you could find that muzzleFlash like this
GameObject muzzleFlashObj = GameObject.Find("muzzleFlash");
ParticleSystem muzzleFlash = muzzleFlashObj.GetComponent<ParticleSystem>();
In your case it's null because there is probably no component that is called MuzzleFlash on that object. The component you want to get is ParticleSystem.
What component is the staticshootingenemy script on? if it is not on the same component as the particle system then its not finding it because this.gameObject.GetComponent("muzzleFLash") does not exist on that component. You can use GameObject.Find("muzzleFLash") to search for the particle system.
So back to your comment, you could implement something like a pool for your muzzle flashes.
public class MuzzleFlashEffect : MonoBehaviour
{
[SerializeField] private ParticleSystem particleEffect;
private Queue<MuzzleFlashEffect> poolQueue;
public void SetPoolQueue(Queue<MuzzleFlashEffect> queue)
{
poolQueue = queue;
}
public void Play()
{
StartCoroutine(Playing());
}
private IEnumerator Playing()
{
particleEffect.Play();
while (particleEffect.isPlaying)
{
yield return null; // wait until particle animation is done, then recycle effect
}
particleEffect.Stop();
poolQueue.Enqueue(this); // recycle this effect
}
// you can do the same thing for Animation as well, or even write some abstract PoolableVFX class that would be usefull for Animation , ParticleSystems etc..
}
//assume you have some game controller that manage what is going on in the scene
public class GameController : MonoBehaviour
{
[SerializeField] private MuzzleFlashEffect muzzleFlashPrefab;
private Queue<MuzzleFlashEffect> poolQueue = new Queue<MuzzleFlashEffect>(10); // 10 is enough i guess and it's good to set it at instantiation to avoid memory fragmentation
private MuzzleFlashEffect GetMuzzleFlash(Vector3 pos, Quaternion rot)
{
MuzzleFlashEffect muzzleFlash;
// if we already have some effects, then play them, otherwise make a new one and recycle it then
if (poolQueue.Count > 0)
{
muzzleFlash = poolQueue.Dequeue();
}
else
{
muzzleFlash = Instantiate(muzzleFlashPrefab);
muzzleFlash.SetPoolQueue(poolQueue);
}
muzzleFlash.transform.position = pos;
muzzleFlash.transform.rotation = rot;
return muzzleFlash;
}
void Update()
{
// your fancy logic ...
GameObject mutantGunEnd = new GameObject("mutant");
//assume that here you want your muzzle flash effect, so you do:
var muzzleFlash = GetMuzzleFlash(mutantGunEnd.transform.position, mutantGunEnd.transform.rotation); // or you might want to pass mutantGunEnd.transform.forward instead, it depends...
muzzleFlash.Play();
// your fancy logic ...
}
}
So, in this case you have only as many instance of ParticleEffect as you need and saving some resources. You could also create a universal generic pool for any type of object you want to recycle. (you want to recycle instead of instantiation, cuz Instantiation is cpu expensive).
M.b this is a bit overkill here, but i just wanted to share how would i think about this here

OnGui elements interacting with other OnGui elements

Within my 2d game i wsih to have a number of OnGui elements there for the user to select, however, the cursor that im using is another ongui element(using kinect to navigate) is this possible by any chance, at the moment im using planes but i will be zooming in and out of the camera so ill essentially need them attatched to the screen. Any ideas, suggestions or workarounds.
THis is currently my cursor.
using UnityEngine;
using System;
using System.Collections;
public class PillarAgent : MonoBehaviour {
public SkeletonWrapper sw;
public Vector3 distance;
public float progress =0f;
public Texture2D cursor;
public Texture2D load;
public Camera mainCam;
public float startTime;
private int roundedRestSecounds;
// Use this for initialization
float differencex = 0;
float differencey = 0;
void Start () {
distance =new Vector3(0f,0f,0f);
}
float translate(float value, float leftMin, float leftMax,
float rightMin,float rightMax)
{
float leftSpan = leftMax - leftMin;
float rightSpan= rightMax - rightMin;
float valueScaled = (value-leftMin)/(leftSpan);
return rightMin+(valueScaled * rightSpan);
}
// Update is called once per frame
void Update () {
if (sw.pollSkeleton())
{
distance.x=sw.bonePos[0,0].x - sw.bonePos[0,7].x;//5 is left shoulder
distance.y=sw.bonePos[0,0].y -sw.bonePos[0,7].y;
differencex=translate(distance.x,.6f,0,0,Screen.width);
differencey=translate(distance.y,-.5f,0,0,Screen.height);
//Debug.Log();
float width = sw.bonePos[0,5].x+ sw.bonePos[0,9].x;
float height =sw.bonePos[0,4].y- sw.bonePos[0,0].y;
float heightdiv= (height/2)+sw.bonePos[0,0].y;
}
}
void OnGUI() {
//left top width height
Rect r = new Rect(differencex,differencey,80,50);
GUI.Label(r,cursor);
GUI.BeginGroup(new Rect(differencex,differencey+50,50*Mathf.Clamp01(progress),15));
//Debug.Log(progress);
GUI.DrawTexture(new Rect(0,0,50,50),load);
GUI.EndGroup();
transform.position =mainCam.ScreenToWorldPoint(new Vector3(differencex,Screen.height-differencey,50));
//mainCam.fieldOfView()
}
void OnCollisionStay(Collision Other)
{
startTime+=Time.deltaTime;
if(Other.gameObject.GetComponent(typeof(TextControl)))
{
roundedRestSecounds=Mathf.CeilToInt(Time.time);
progress = Time.time *0.2f;
CurrentState=true;
}
else if(Other.gameObject.tag==("Scalpal")){
progress = startTime *0.5f;
//scallpall activated
//
}
}
void OnCollisionExit(Collision Other){
startTime =0f;
progress =0f;
}
public Boolean CurrentState{get;set;}
}
The next class is essentially the class in which i pick up my tools, currently this code doesnt work(not sure why), but what i wish to do is select some tools which show up on the screen so that i can use them,for e.g pick up paint brush start painting bricks or what not. at the moment i have my tools on a plane, i wish to always have them on the screen at all times when the camera moves.
using UnityEngine;
using System.Collections;
public class SelectTool : MonoBehaviour {
public Tools tools;
public float startTime;
public bool ScalpalSelected;
public GameObject selectedTool;
void Start()
{
tools = this.GetComponent<Tools>(); //in order to use this tools muyst be attached to the game object
//this is essentially saying with regards to this game object get the component named tools
}
void update()
{
}
void OnCollisionStay(Collision Other)
{
startTime +=Time.deltaTime;
if(startTime >5f){
if(Other.collider.tag==("Scalpal"))
{
selectedTool = Other.collider.gameObject;
Debug.Log(selectedTool+" What in gods good name is:" +tools.utilities[0]);
}
else {
selectedTool=null;
}
if(selectedTool){
for(int i=0;i<tools.utilities.Length;i++)
{
}
}
ScalpalSelected=true;
renderer.material.color = Color.yellow;
}
}
void OncollisionStay(Collision other){
startTime = 0f;
}
}
From the comment section to the question I will assume that you want to know how to do this:
"... you want your plane objects to move together with the camera ..." - Steven Mills
"thank you #StevenMills Do you have any example of how this can be done?" j bel
While the answer provided in the comments is to just manually add the planes as children of the camera (a very straightforward, manual approach), I will give another way to do this through scripting (in light of maybe this will help someone else out disregarding the likeliness of someone using this solution).
The idea of this is to create a script (thus following being attached to the MainCamera) that will search through all of the GameObject in the Object Hierarchy with the method GameObject.FindGameObjectsWithTag. Once we have all our GameObject with the associated Tag, we can then loop through the array and parent the attached script's GameObject to each.
public class ParentGameObjects : MonoBehaviour {
//The tag to search for on all game objects in the hierarchy
public String objectTag;
//The game objects that we will parent the main camera to
private GameObject[] children;
//It's not necessary to store references to the children but if you want to modify them at some point you will be able to
void Start() {
//Find all game objects with the tag we want
children = GameObject.FindGameObjectsWithTag(objectTag);
//Loop through all of the game objects found and parent this object's transform
for(int i = 0; i < children.Length; i++) {
children[i].transform.parent = transform;
}
}
}
Now, there are a few things you have to do for this script to work:
Attach this script to any GameObject that you want to parent other objects to.
In the Inspector of the GameObject that the script is attached to, enter in the name of the Tag you want to use.
For all GameObject(s) in the Hierarchy that should be added as children, assign the same Tag.
Of course there are other things you can do like, for example, instead of only searching for one Tag be able to search for multiple but that requires a bit (not exactly much) more work. Nonetheless, I hope this will at least be useful information to someone on how parenting works via scripting.

Categories