Input.GetMouseButtonUp not working as expected in Unity - c#

I have a 2D Unity game with numerous agents that can be selected when they are clicked on. When this happens they become the player (the player's agent variable is replaced with them and the agent becomes the player's parent). I am trying to implement a system where you drag their destination over another agent to "Target" them.
To do this I am actually targeting from within the target's My class (a class that holds references to all the player's components and variables) so I am putting the agent into a function that is operated by the player's current agent. This seems to work in theory, but in Unity it is only allowing me to target the first agent in the hierarchy, and none of the others. But all the agents are identical instances of the agent prefab, generated at the start of the game.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Agent : Grammar {
public Physical physical;
public Agent agent;
void Update() {
float distanceFromMouse = the.Distance(this.transform.position, Camera.main.ScreenToWorldPoint(Input.mousePosition));
if (Input.GetMouseButtonDown(0)) {
if (distanceFromMouse < 1f) {
the.player.agent = this;
the.player.transform.parent = this.transform;
}
}
if (Input.GetMouseButtonUp (0)) {
if (distanceFromMouse < 1f) {
if (the.player.agent != this && the.player.agent is Agent) {
the.player.agent.Target (agent);
}
}
the.player.agent = null;
}
if (the.player.agent == agent)
physical.goal = Camera.main.ScreenToWorldPoint (Input.mousePosition);
else if (physical.hasTarget)
physical.goal = physical.target.transform.position;
}
}
public void Target (My target) {
my.physical.target = target;
my.physical.hasTarget = true;
foreach (My other in the.agents.GetComponentsInChildren<My>()) {
if (other.physical.targetters.Contains (my))
other.physical.targetters.Remove (my);
}
target.physical.targetters.Add (my);
target.physical.targetted = true;
}
Here is my Grammar class that everything inherits from so they can access the global The class.
using UnityEngine;
using System.Collections;
public class Grammar : MonoBehaviour {
public A a;
public The the;
}
Here is my The (global utility) class
using UnityEngine;
using System.Collections;
public class The : MonoBehaviour {
public Grid grid;
public GameObject world;
public Player player;
public GameObject squareParent;
public GameObject linkParent;
public GameObject nodeParent;
public GameObject agents;
public Vector2 HeadingFrom(float angle) { return new Vector2(Mathf.Cos(angle * Mathf.Deg2Rad), Mathf.Sin(angle * Mathf.Deg2Rad)); }
public float AngleFrom(Vector2 heading) { return Mathf.Atan2 (heading.y, heading.x) * Mathf.Rad2Deg; }
public Vector2 RelativeHeadingFrom(Vector2 heading, float rotation) { return (Vector2)(HeadingFrom(AngleFrom(heading) - rotation)); }
public float Distance(Vector2 from, Vector2 to) { return (Heading(from,to)).magnitude; }
public Vector2 Heading (Vector2 from, Vector2 to) { return to - from; }
public Vector2 MidPoint (GameObject my, GameObject their) { return (my.transform.position + their.transform.position) / 2; }
}

The way you are detecting the clicks in your agent class is a bit off. While the property Input.mousePosition returns a Vector3 the Z component is always zero. The documentation for the Camera.ScreenToWorldPoint function notes that the Z component of the supplied vector is the distance in world-units from the camera, this means that the world-position of your "clicks" in the Agent class will always be right next to the camera.
The usual approach is to either define the methods OnMouseDown() and OnMouseUp() (in your Agent class) or use Camera.ScreenPointToRayto create a Ray which is then used to do a raycast into the scene and then using the ray hit-result as the 'actual world' position of the mouse pointer. See this answer on Unity Answers for more about that.
I suggest you use the new event system (which is part of the new Unity GUI system) to detect if an agent has been clicked instead as this also handles touch input in case you want to your game to work on a mobile device.
What you would need to do to do that is:
Implement the interface IPointerUpHandler and IPointerDownHandler in your agent class and handle the clicks in the methods the interfaces require you to define.
Make sure you have some kind of collider (or trigger) on your agent.
Make sure you add a PhysicsRaycaster component on your Camera object.
You might also be interested in these interfaces:
IBeginDragHandler
IDragHandler
IEndDragHandler
IDropHandler

Related

Obtaining data from selected object

I'm looking for a way to obtain data from an object I have "selected/targeted" in game. Example: I want to select an enemy to attack and then take the hp value from the script attatched to them and compare it to the attack value on the player script. I've looked around on google and youtube but have found no luck. Hoping someone could help me or point me to a guide to look at.
Example in code form:
Script 1:
public class Enemy : MonoBehaviour
{
public int enemyHealth;
}
Script 2:
public class Player : MonoBehaviour
{
public Enemy enemy;
public int playerAtk;
}
public void Attack()
{
"Selected enemy's enemy script".health -= playerAtk;
}
You must access the enemy through a specific event. For example, when hitting a bullet or clicking the mouse on it. Below are some examples of how to get the enemy, but keep in mind that there are countless ways to access the enemy.
On Trigger Enter
This is a very simple way, This code works in the player class. The collider key gives you access to the enemy, now you can access the methods by holding its component.
public void OnTriggerEnter(Collider other)
{
var _enemy = other.GetComponent<Enemy>(); // // Get Enemy class
_enemy?.Damage(10); // damage method run.
_enemy?.Damage(this, 10f); // Send player to method and cause damage
}
Physics Raycast
This will happen by throwing a ray from the camera, what the raycast code does is return the point of impact in raycastHit format. After getting it, you can access your other raycastHit components as shown below.
public void Update()
{
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (!Input.GetKeyDown(KeyCode.Mouse0)) return;
if (Physics.Raycast(ray, out var RaycastHit))
{
var _enemy = RaycastHit.transform.GetComponent<Enemy>();
_enemy?.Damage(10);
}
}
Specify in the Inspector
In this method, you can put all the enemies in an array and damage them by calling the index.
public Enemy[] myEnemy;
public void Damage(int index)
{
myEnemy[index].Damage(10);
}
Find By Type
Another popular method is to capture all enemies and filter them based on a specific condition. This method is also performed in this way.
var enemies = FindObjectsOfType<Enemy>();
foreach (var _enemy in enemies.Where(e => e.health >= 40f))
{
_enemy.Damage(100);
}

How do i add the objects that the raycast is hitting in unity and add them to a list

I have this problem that i just can't figure out how to solve.
I am trying to make a game in Unity, and i have stumbled across a problem that goes like this.
I want to put an object in the scene in a list, when i hit it with a raycast.
With what i have tried so far. Either it puts everything that is tagged the same thing on the list when i press mousebutton on one of the objects, or it only puts in the same thing (Cube in this example).
My code is:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerTagged : MonoBehaviour
{
public float damage = 10f;
public float range = 100f;
public Camera fpsCam;
public List<GameObject> playersTagged;
private void Update()
{
if (Input.GetButtonDown("Fire1"))
{
Shoot();
}
}
void Shoot()
{
RaycastHit hit;
if (Physics.Raycast(fpsCam.transform.position, fpsCam.transform.forward, out hit, range))
{
Debug.Log(hit.transform.name);
Target target = hit.transform.GetComponent<Target>();
if (target != null)
{
target.takeDamage(damage);
if(hit.collider.tag == "Taggable")
playersTagged.Add(GameObject.FindWithTag("Taggable"));
}
}
}
}
use
playersTagged.Add(hit.collider.gameObject);
Instead of using GameObject.FindWithTag() which returns the first object it'll find source
Use gameobject property playersTagged.Add(target.gameobject). Since all components store the reference to the gameobject they are attached to.
You are adding GameObject.FindWithTag("Taggable") which means you are getting any GameObject that has that tag and adding it to the list. But instead, you want to add the hit object, so you need to add hit.collider.gameObject.

How can I reference a game object on my scene in a prefab component I have just instantiated?

Currently I am learning both Unity and C#. I am working on a small game where I control a spaceship on the screen, while meteors are spawning in a random position outside of the camera's visible space, then start to move towards the ship.
For this I am trying to refer to the ship game object in a component on a meteor prefab, to get it's position and the angle needed to start moving towards the ship.
public class meteorPath : MonoBehaviour
{
public float speed = 10f;
public Rigidbody2D rb;
public GameObject ship;
void Start()
{
int typeOf = Random.Range(2, 11);
Vector3 posOf = new Vector3(Random.Range(-7f, 7f), Random.Range(16f, 8f), 0);
transform.localScale = new Vector3(typeOf, typeOf, 1);
transform.localPosition = posOf;
}
}
How can I make get the player's position to calculate the angle needed every time I instantiate a meteor?
I'm guessing that you're instantiating everything at runtime.
You would either do:
GameObject.Find(YOUR OBJECT NAME)
GameObject.FindGameObjectWithTag(sometag), only if your player has a tag
Make player's class singleton and reference it from other classes with PlayerClassName.InstanceName and reference the position.
If you're not instantiating everything but is a static scene, then just make a prefab of the meteor with the player's reference inside
one way to get references is to create instanced game manager:
and when meteor is created you have it get player position from that instanced GameManager.
in start of meteor:
private Vector3 playerPosition = GameManager.instance.playerPosition.transform.position;
in GameManager:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public static GameManager instance;
public GameObject playerPosition;
private void Awake()
{
instance = this;
}

how follow the instantiated object to other instantiated object

i want to follow the instantiated ball to my instantiated player. when my ball follow the player from hierarchy it work fine but when i instantiate the player it does not work .
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ExtraBallController : MonoBehaviour
{
public Transform target;
private int distance = 30;
// Use this for initialization
void Start ()
{
}
// Update is called once per frame
void Update ()
{
this.transform.position = new Vector3 (target.transform.position.x, target.transform.position.y, distance);
}
}
As #Hellium said, there are numerous possibilities. One way is to set the balls' target when the player is instantiated.
Here's a sample code (Not Tested):
GameObject player = Instantiate(playerPrefab, vectorPos, Quaternion.Identity);
ExtraBallController[] balls = FindObjectsOfType(typeof(ExtraBallController)) as ExtraBallController[];
foreach(ExtraBallController ball in balls){
ball.target = player.transform;
}
Hope this helps!

Unity, C#. How to get the all the children of a parent on runtime?

I am making a puzzle using drag & drop in unity. The puzzle piece is like Tetris-piece, each puzzle piece is composed of a group of cubes. the puzzle piece needs to be dragged and then dropped into the slots of missing cubes of the structure(e.g. rectangle).
Screenshot of the game
The screenshot above illustrates that I have a group of cubes named "block1" that has 3 children/cubes, it also illustrates that the game has slots wherein the cubes needs to be dropped. When I drag "block1" and then dropped into the slots it only occupies one slot knowing that it has 3 cubes. What I want is when I dropped the "block1" (which has 3 three cubes) into the slots I want each cube to occupy each of the slot (3 slots). So, what I think I need to do is to get only the children of block1 when I dropped it on the slots. When I drag it I drag a group but when I dropped only the children will get so that every cube will fill a slot. My question is, how to get all the children of the block1 on dropped? In my slothandler scipt it gets the whole block/piece. Thanks in advance!
Here's the SlotHandler Script
using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class SlotHandler : MonoBehaviour, IDropHandler{
public GameObject item {
get {
if(transform.childCount>0){
return transform.GetChild (0).gameObject;
}
return null;
}
}
#region IDropHandler implementation
public void OnDrop (PointerEventData eventData)
{
if(!item){
DragHandler.piece.transform.SetParent (transform);
}
}
#endregion
}
and the DragHandler Script
using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
public class DragHandler : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler{
public static GameObject piece;
Vector3 startPosition;
Transform startParent;
#region IBeginDragHandler implementation
public void OnBeginDrag (PointerEventData eventData)
{
piece = gameObject;
startPosition = transform.position;
startParent = transform.parent;
GetComponent<CanvasGroup>().blocksRaycasts = false;
}
#region IDragHandler implementation
public void OnDrag (PointerEventData eventData)
{
transform.position = eventData.position;
}
#endregion
#region IEndDragHandler implementation
public void OnEndDrag (PointerEventData eventData)
{
piece = null;
GetComponent<CanvasGroup>().blocksRaycasts = false;
if(transform.parent == startParent){
transform.position = startPosition;
GetComponent<CanvasGroup>().blocksRaycasts = true;
}
}
#endregion
}
you can add colliders to the slots, and in the collision listener, check if it's completely in place, and then change the parent like this:
transform.parent = [Parent Slot GameObject or Transform]
but if you are gonna go your way, still it's ok.I'm not an expert in this kind of game, so ...
but the main code stays still. change the parent with the one line code above.
if you want the cube to have no parent at all, you can say:
transform.parent = null;
or if you have a parent object for all of these, as I say in your screenshot(blocks, panel or canvas), you can add that as a public object to script and make that the parent.
public GameObject parent;
...
// when you wanna ungroup the objects
transform.parent = parent;

Categories

Resources