The particles spawn on the wrong place - c#

I'm making a game in Unity where you can chop down trees, I want to make it so that particles spawn where you hit the tree. At this point the particles spawn where the player is, this is because the script is on the player. But how can I spawn the particles in the right place? (Where I hit the tree) It's probably not even that hard to solve, but I can't figure it out. My current C# code is below.
public class ChopTree : MonoBehaviour
{
public int damage = 25;
public Camera FPSCamera;
public float hitRange = 2.5f;
private TreeScript Tree;
// Particles
public GameObject particles;
void Update()
{
Ray ray = FPSCamera.ScreenPointToRay(new Vector2(Screen.width / 2, Screen.height / 2));
RaycastHit hitInfo;
if(Input.GetKeyDown(KeyCode.Mouse0))
{
if(Physics.Raycast(ray, out hitInfo, hitRange))
{
// The tag must be set on an object like a tree
if(hitInfo.collider.tag == "Tree" && isEquipped == true)
{
Tree = hitInfo.collider.GetComponentInParent<TreeScript>();
StartCoroutine(DamageTree());
StartCoroutine(ParticleShow());
}
}
}
}
private IEnumerator DamageTree()
{
// After 0.3 seconds the tree will lose HP
yield return new WaitForSeconds(0.3f);
Tree.health -= damage;
}
private IEnumerator ParticleShow()
{
// After 0.3 second the particles show up
yield return new WaitForSeconds(0.3f);
Instantiate(particles, transform.position, transform.rotation);
}
}

Well instead of
Instantiate(particles, transform.position, transform.rotation);
make sure you use the hit tree positions like
Instantiate(particles, Tree.transform.position, transform.rotation);
Actually personally I would merge both Coroutines together and pass in the according tree:
private IEnumerator ChopTree(TreeScript tree)
{
// After 0.3 seconds the tree will lose HP
yield return new WaitForSeconds(0.3f);
Instantiate(particles, tree.transform.position, transform.rotation);
tree.health -= damage;
}
and then
void Update()
{
var ray = FPSCamera.ScreenPointToRay(new Vector2(Screen.width / 2, Screen.height / 2));
if(Input.GetKeyDown(KeyCode.Mouse0))
{
if(Physics.Raycast(ray, out var hitInfo, hitRange))
{
// The tag must be set on an object like a tree
if(hitInfo.collider.CompareTag("Tree") && isEquipped)
{
var tree = hitInfo.collider.GetComponentInParent<TreeScript>();
if(tree)
{
StartCoroutine(ChopTree(tree));
}
}
}
}
}

If you are chopping the tree by clicking to screen then, you could spawn it where "hit" object is but, if you try to instantiate particle in place of hit object it would be a tree's origin so, you could add tree's collider to the surface of the tree and make it different object (Also you can make it child). So this way is not very smooth but you could create particles on the surface of the tree by this way.
Also if you are chopping it with character then, you could add collider which have enable onTrigger to tree and when you triggered you spawn a particle at where triggered object is.

Related

Unity-3D, Enemy using Raycast and NavMesh keeps seeing me through walls

I've been trying to set an enemy on a patrol path while not chasing my player character. I want to use RayCast so that the enemy can spot the player and begin to chase the player. It functions as I intended. However, even when there's an obstacle or wall between us, or when I approach from behind, the enemy 'sees' my player and starts to chase me. It seems to ignore the Raycast, and instead focuses on the proximity to the enemy.
Enemy spotting player through wall
public class EnemyController : MonoBehaviour
{
private NavMeshAgent enemy;// assaign navmesh agent
private Transform playerTarget;// reference to player's position
private float attackRadius = 10.0f; // radius where enemy will spot player
public Transform[] destinationPoints;// array of points for enemy to patrol
private int currentDestination;// reference to current position
public bool canSeePlayer = false;
private Ray enemyEyes;
public RaycastHit hitData;
private void Awake()
{
enemy = GetComponent<NavMeshAgent>();
playerTarget = GameObject.Find("Player").GetComponent<Transform>();
enemyEyes = new Ray(transform.position, transform.forward);
}
private void Start()
{
Physics.Raycast(enemyEyes, attackRadius);
}
private void Update()
{
Lurk();
Debug.DrawRay(transform.position, transform.forward * attackRadius);
}
void Lurk()
{
Debug.Log("Lurking");
float distanceToPlayer = Vector3.Distance(transform.position, playerTarget.position);
//check if raycast hits playerLayer and enemy is close enough to attack
if (Physics.Raycast(enemyEyes, out hitData, attackRadius * 2, layerMask: ~6) && distanceToPlayer < attackRadius)
{
Debug.Log("You hit " + hitData.collider.gameObject.name);
ChasePlayer();
}
else
{
canSeePlayer = false;
Patrol();
}
}
void Patrol()
{
if (!canSeePlayer && enemy.remainingDistance < 0.5f)
{
enemy.destination = destinationPoints[currentDestination].position;
UpdateCurrentPoint();
}
}
void UpdateCurrentPoint()
{
if (currentDestination == destinationPoints.Length - 1)
{
currentDestination = 0;
}
else
{
currentDestination++;
}
}
void ChasePlayer()
{
StartCoroutine(ChaseTime());
canSeePlayer = true;
transform.LookAt(playerTarget.position);
Vector3 moveTo = Vector3.MoveTowards(transform.position, playerTarget.position, attackRadius);
enemy.SetDestination(moveTo);
}
IEnumerator ChaseTime()
{
Debug.Log("Chasing");
yield return new WaitForSeconds(10.0f);
if (!Physics.Raycast(enemyEyes, out hitData, attackRadius * 2, layerMask: ~6))
{
canSeePlayer = false;
Debug.Log("Lurking");
Lurk();
}
}
}
I've removed the tilde "~" for the layermask, but then the enemy doesn't ever see the player.
I've initialised and set a layer mask reference to the 'playerLayer' and used it in place of "layermask: ~6", to no avail.
I've used the int reference to the Player layer, to no avail.
I've used bitwise operator to reference the player layer, to no avail.
I've removed the distanceToPlayer conditional, but the enemy doesn't see the player.
I've adjusted the length of the Ray but if it's ever shorter than the attack radius, it doesn't work.
I don't understand why you need a layer mask. If you want the Raycast to hit something in front of the player then you must include all layers in the Raycast.
What you need to do is remove the layermask from Raycast and in the if statement check if the out hit collider is on Player and if the player is in attack radius. Here is a simple outline
If(raycast)
{
if(hit.collider.gameobject==player && player in proximity)
{
Then can see is true
}
}
You can give this article on Unity Raycast a read to understand more on layermask.
regarding player detection through a wall,I would try adding an extra if statement in the Lurk method.This condition would check if the raycast is touching the wall, if so, do not proceed with the method, if not, continue. Sorry for giving the code in this form but I don't have access to a computer and the phone doesn't want to cooperate
enter image description here

How to fix spasming and buggy instantiation in Unity

I'm creating a basic pool game in Unity with C#, what im trying to do is that if the cue ball is moving, the stick will disappear, and once it becomes stationary again, it will reappear to where the cue ball is located. This is my code so far:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class stickDeplacement : MonoBehaviour
{
public bool bIsOnTheMove = false;
Vector3 lastPos;
public GameObject Stick;
void Start()
{
}
void Update()
{
var stick = Instantiate(Stick, gameObject.transform.position, gameObject.transform.rotation);
if (this.transform.position != lastPos)
{
Destroy(stick);
Debug.Log("Is moving");
}
else
{
Debug.Log("Is not moving");
}
lastPos = this.transform.position;
}
}
But what happens is that the ball, along with the stick, will just spasm and be buggy right from the start (when I open and play the game). Am I missing something here?
This is extremely inefficient and dangerous!
Why instantiate a stick every frame just to eventually already destroy it in that very same frame? And if the ball is stationary you want an additional stick to be spawned every frame?
Instead of all the time instantiating and destroying it at all you should rather keep one stick and only (de)activate it.
In your case you could do this in a single line
bIsOnTheMove = transform.position == lastPos;
stick.SetActive(!bIsOnTheMove);
Also I doubt a lot you would like the stick to have the same rotation as a rolling ball! Of course this will behave awkward
Most certainly you do not simply want to clone the ball's orientation. I would e.g. try to determine the closest point of the table edge to the current ball's position (iterate through the wall colliders and use Collider.ClosestPoint) and let the stick face the direction from that edge point towars the ball position (+ maybe an offset in X so the stick is slightly inclined by default).
And finally you anyway do not want to assign that rotation every frame, since you most probably later want your users to be able to rotate that stick. You only want to apply this once when the ball becomes stationary.
Something like e.g.
// The stick is not a prefab anymore but simply always exists in the scene!
[SerializeField] private Transform stick;
[SerializeField] private Vector3 eulerOffset;
[SerializeField] private Collider[] wallColliders;
public bool bIsOnTheMove;
private Vector3 lastPos;
private void Start()
{
lastPos = transform.position;
}
private void Update()
{
// is the ball currently moving?
var isMoving = transform.position == lastPos;
last Post = transform.position;
// Did this state change since the last frame?
if(bIsOnTheMove == isMoving) return;
bIsOnTheMove = isMoving;
// (de)activate the stick accordingly
stick.gameObject.SetActive(!isMoving);
// Do this ONCE when ball becomes stanionary
if(!isMoving)
{
var ballPosition = transform.position;
// among the wall colliders find which one is closest to the ball
Vector3 closestPoint;
var smallestDistance = float.PositiveInifinity;
foreach(var wall in wallColliders)
{
var edgePoint = wall.ClosestPoint(ballPosition);
var distane = (edgePoint - ballPosition).sqrMagnitude;
if(distance < smallestDistance)
{
closestPoint = point;
smallestDistance = distance;
}
}
// then make the stick look towards the ball from that edge
var direction = ballPosition - closestPoint;
var rotation = Quaternion.LookRotation(direction);
// optional add the offset
rotation *= Quaternion.Euler(eulerOffset);
stick.rotation = rotation;
}
}

Bullet not shooting enemy center in unity

can you please help me to create correctly shoot of bullet to enemy center with my method ?
private void Update()
{
if (GameObject.FindGameObjectWithTag("Enemy") && !_shooting)
{
StartCoroutine(Shoot());
_shooting = true;
}
}
private IEnumerator Shoot()
{
int random = Random.Range(1, Camera.main.transform.childCount);
int rnd = Random.Range(0, transform.childCount);
Instantiate(Bullet, transform.parent);
target = Camera.main.transform.GetChild(1).transform.position;
var rb = transform.parent.GetChild(transform.parent.childCount - 1).GetComponent<Rigidbody2D>();
rb.transform.position = positions[rnd];
transform.GetChild(rnd).GetComponent<Transform>().localScale = new Vector2(1.2f, 1.2f);
StartCoroutine(ReturnDotSize(rnd));
while (Vector2.Distance(rb.transform.position, target) > 0.1f)
{
rb.transform.position = Vector2.MoveTowards(rb.transform.position, target, shootSpeed * Time.deltaTime);
yield return null;
}
yield return new WaitForSeconds(ReloadTime);
_shooting = false;
}
Now bullet shoot only one time. I was try to write break instead of yield return null, but in this time bullet not shoot to center of enemy.In addition, I tried to write "_shooting = false" over "yield return null" and in this case the bullet clearly goes to the center of the enemy, but the "ReloadTime" delay does not work. Now I need the bullet to accurately go to the center of the enemy and work correctly "ReloadTime".
For customiztion sake and performance reasons, you should create a script Enemey.cs attached to your Enemey GameObject that has a public transform field named "BulletTarget". This Bullet Target Transform is a child element of your Enemey game object and you can then freely place it where you see the center of your enemy object. Perfomance wise you cen then use FindObjectsOfType which is better and less fragile then by name.

Click to move code works in one scene but not the rest, NullReferenceException error

This is my click to move code, This is my first question, so I'm new to this. If you need any more information I will be happy to give it to you!
I have made a top-down dungeon game (Like Diablo), I completed creating all of the dungeon levels and started to animate and move my player, I got it working starting on the third and last level and it works perfect, I was happy as this is my first time making a game. I created a prefab of the character and moved it into the other levels and only got error when I clicked to move, I have tried to add them in separately but still didn't work sadly.
using UnityEngine;
using System.Collections;
public class ClickToMove : MonoBehaviour
{
public float speed;
public CharacterController controller;
private Vector3 position;
public AnimationClip idle;
public AnimationClip run;
public static Vector3 cursorPosition;
// Use this for initialization
void Start ()
{
position = transform.position;
}
// Update is called once per frame
void Update ()
{
if(Input.GetMouseButton(0))
{
//Locate where the player clicked on the terrain
locatePosition();
}
//Move the player to the position
moveToPosition();
}
void locatePosition()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if(Physics.Raycast(ray, out hit, 1000))
{
if(hit.collider.tag!="Player"&&hit.collider.tag!="Enemy")
{
position = hit.point;
}
}
}
void locateCursor()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if(Physics.Raycast(ray, out hit, 1000))
{
cursorPosition = hit.point;
}
}
void moveToPosition()
{
//Game Object is moving
if(Vector3.Distance(transform.position, position)>1)
{
Quaternion newRotation = Quaternion.LookRotation(position- transform.position, Vector3.forward);
newRotation.x = 0f;
newRotation.z = 0f;
transform.rotation = Quaternion.Slerp(transform.rotation, newRotation, Time.deltaTime * 10);
controller.SimpleMove(transform.forward * speed);
GetComponent<Animation>().CrossFade("run");
}
//Game Object is not moving
else
{
GetComponent<Animation>().CrossFade("idle");
}
}
}
As Catwood said, double clicking on the error in the console will take you to the relevant line of code that's causing the exception. I'd expect it to be one of the GetComponent calls though as you're trying to call the CrossFade function on something that might return null (in the case that the Animator component can't be found).
As a side note, you should avoid using GetComponent like this as it's inefficient. Instead, create a private / protected variable and store the reference when you first get the component.

Can't access variable in different script: Unity C#

Currently I'm making my first video game using unity written in C#, but now I'm facing a trouble and I don't know what to do anymore. First I already made my character move but, I want to put all the attributes in one script, like it's moveSpeed, attackSpeed, etc. but once I access it to another script, my character just stands and do nothing. Here's my code
public class ClickToMove : MonoBehaviour {
public CharacterController controller; // Use to move the player
private Vector3 position; // Store the position at which the player clicked;
public Attributes attribute;
// Animation variables
public AnimationClip idleClip;
public AnimationClip runClip;
// Use this for initialization
void Start () {
position = transform.position; // Set the position to player's current position
}
// Update is called once per frame
void Update () {
// Execute the code below once the player press left click
if(Input.GetMouseButton(0)) {
locatePosition();
}
moveToPosition();
}
// Locate at which the player clicked on the terrain
private void locatePosition() {
RaycastHit hit; // Get information from ray
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); // A line in 3D space
// Cast a ray that start from the camera with a distance of 1000
if(Physics.Raycast(ray, out hit, 1000)) {
// Store the position if the casted ray is not pointing to the player or enemy's position
if(hit.collider.tag != "Player" && hit.collider.tag != "Enemy") {
position = new Vector3(hit.point.x, hit.point.y, hit.point.z);
}
}
}
// Move to the located position
private void moveToPosition() {
// Check if the player position and the destination is greater than one
if(Vector3.Distance(transform.position, position) > 1) {
// Subtract the clicked position to the player position
Quaternion newRotation = Quaternion.LookRotation (position - transform.position);
// Disable the rotation from x and z angle
newRotation.x = 0f;
newRotation.z = 0f;
// Rotate the player to a new rotation then move forward
transform.rotation = Quaternion.Slerp(transform.rotation, newRotation, Time.deltaTime * attribute.moveSpeed);
controller.SimpleMove(transform.forward * attribute.moveSpeed);
animation.CrossFade(runClip.name);
} else {
animation.CrossFade(idleClip.name);
}
}
}
and here is the script I'm accessing
public class Attributes : MonoBehaviour {
public float moveSpeed;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
}
I don't know what to do anymore. Any help will be appreciated. Thank you.
There are a couple of things that you need to double check, the first being that you are actually assigning a value to attribute.moveSpeed in the inspector. Otherwise moveSpeed will always be 0 and therefore all of your multiplication operations will result in 0, resulting in no movement at all.
Second, make sure that something is actually being hit by your raycast. You can do this a couple ways, first by printing a message to the console saying that you hit something
// Cast a ray that start from the camera with a distance of 1000
if(Physics.Raycast(ray, out hit, 1000)) {
// Send a debug message to Unity's console
Debug.Log("I hit Something!");
// Store the position if the casted ray is not pointing to the player or enemy's position
if(hit.collider.tag != "Player" && hit.collider.tag != "Enemy") {
position = new Vector3(hit.point.x, hit.point.y, hit.point.z);
}
}
and second by checking that the position variable changed values in the inspector. (Make the position variable public and look at your character's inspector)
Note that you've assigned a value to your attribute variable in the inspector because you're not getting NullReferenceExceptions

Categories

Resources