I have to control a shot of a ball with touch. All works fine but I need to start my trajectory when I touch the screen, but the trajectory start only when the ball is touched. Is because the script is attached to the ball?
All touch input except touches on the ball is ignored.
Here is the C# script , can someone help me?
using UnityEngine;
using System.Collections;
using System;
namespace Shorka.BallTrajectory
{
public enum PullState
{
Idle,
UserPulling,
ObjFlying
}
public class PullCtrl : MonoBehaviour
{
#region fields
//[SerializeField] private Transform throwTarget;
[SerializeField] private ThrownObject throwObj;
[SerializeField] private Transform dotHelper;
[SerializeField] private Transform pullingStartPoint;
[Space(5)]
[Tooltip("this linerenderer will draw the projected trajectory of the thrown object")]
[SerializeField]
private LineRenderer trajectoryLineRen;
[SerializeField]
private TrailMaker trail;
[Space(5)]
[SerializeField]
private float throwSpeed = 10F;
[Tooltip("Max Distance between 'PullingStartPoint' and pulling touch point")]
[SerializeField]
private float maxDistance = 1.5F;
[SerializeField]
private float coofDotHelper = 1.5F;
[Tooltip("Related to length of trajectory line")]
[SerializeField]
private int qtyOfsegments = 13;
[Tooltip("Step of changing trajectory dots offset in runtime")]
[SerializeField]
private float stepMatOffset = 0.01F;
[Tooltip("Z position of trajectory dots")]
[SerializeField]
private float dotPosZ = 0F;
private PullState pullState;
private Camera camMain;
//private Collider2D collThrowTarget;
private Rigidbody2D rgThrowTarget;
private Vector3 posPullingStart;
private Vector3 initPos;
private TrajectoryCtrl trajCtrl;
#endregion
public Vector3 PosDotHelper { get { return dotHelper.position; } }
public Vector3 PosThrowTarget { get { return throwObj.transform.position; } }
public int QtyOfsegments { get { return qtyOfsegments; } }
public float DotPosZ { get { return dotPosZ; } }
public Vector3 PosPullingStart { get { return posPullingStart; } }
public float StepMatOffset { get { return stepMatOffset; } }
void Awake()
{
trail.emit = false;
trajCtrl = new TrajectoryCtrl(this, trajectoryLineRen);
}
void Start()
{
camMain = Camera.main;
pullState = PullState.Idle;
posPullingStart = pullingStartPoint.position;
initPos = PosThrowTarget;
}
void Update()
{
SwitchStates();
}
private void SwitchStates()
{
switch (pullState)
{
case PullState.Idle:
if (Input.touchCount> 0 && Input.GetTouch(0).phase == TouchPhase.Began)
{
Debug.Log("Screen touched");
//get the point on screen user has tapped
Vector3 location = camMain.ScreenToWorldPoint(Input.GetTouch(0).position);
//if user has tapped onto the ball
if (throwObj.Collider == Physics2D.OverlapPoint(location))
pullState = PullState.UserPulling;
}
break;
case PullState.UserPulling:
dotHelper.gameObject.SetActive(true);
if (Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Moved)
{
//get touch position
Vector3 touchPos = camMain.ScreenToWorldPoint(Input.GetTouch(0).position);
touchPos.z = 0;
//we will let the user pull the ball up to a maximum distance
if (Vector3.Distance(touchPos, posPullingStart) > maxDistance)
{
Vector3 maxPosition = (touchPos - posPullingStart).normalized * maxDistance + posPullingStart;
maxPosition.z = dotHelper.position.z;
dotHelper.position = maxPosition;
}
else
{
touchPos.z = dotHelper.position.z;
dotHelper.position = touchPos;
}
float distance = Vector3.Distance(posPullingStart, dotHelper.position);
trajCtrl.DisplayTrajectory(distance);
}
else if (Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Canceled || Input.GetTouch(0).phase == TouchPhase.Ended)
{
float distance = Vector3.Distance(posPullingStart, dotHelper.position);
trajectoryLineRen.enabled = false;
ThrowObj(distance);
}
break;
default:
break;
}
}
//private Vector2 velocityToRg = Vector2.zero;
private void ThrowObj(float distance)
{
Debug.Log("ThrowObj");
pullState = PullState.Idle;
Vector3 velocity = posPullingStart - dotHelper.position;
//velocityToRg = CalcVelocity(velocity, distance);
throwObj.ThrowObj(CalcVelocity(velocity, distance));
//rgThrowTarget.velocity = velocityToRg;
//rgThrowTarget.isKinematic = false;
trail.enabled = true;
trail.emit = true;
dotHelper.gameObject.SetActive(false);
}
public void Restart(Vector3 posThrownObj)
{
trail.emit = false;
trail.Clear();
StartCoroutine(ClearTrail());
trajectoryLineRen.enabled = false;
dotHelper.gameObject.SetActive(false);
pullState = PullState.Idle;
throwObj.Reset(posThrownObj);
}
private readonly WaitForSeconds wtTimeBeforeClear = new WaitForSeconds(0.3F);
IEnumerator ClearTrail()
{
yield return wtTimeBeforeClear;
trail.Clear();
trail.enabled = false;
}
Vector3 velocity = Vector3.zero;
public Vector3 CalcVelocity(Vector3 diff, float distance)
{
velocity.x = diff.x * throwSpeed * distance * coofDotHelper;
velocity.y = diff.y * throwSpeed * distance * coofDotHelper;
return velocity;
}
}
}
Its quite easy. you need to add these two scripts to the gameobject (not the ball but the one which is used to shoot the ball something like a gun)
TouchEventTrigger.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
public class TouchEventTrigger : MonoBehaviour, IPointerClickHandler, IPointerDownHandler, IPointerUpHandler {
public TouchEvent onClick;
public TouchEvent onDown;
public TouchEvent onUp;
public void OnPointerClick(PointerEventData e) {
onClick.Invoke(e);
}
public void OnPointerDown(PointerEventData e) {
onDown.Invoke(e);
}
public void OnPointerUp(PointerEventData e) {
onUp.Invoke(e);
}
}
[System.Serializable]
public class TouchEvent : UnityEvent<PointerEventData> {}
Shooter.cs
public void TryBeginAim(PointerEventData e) {
if(canShoot) {
initialTouchPos = e.position;
}
}
public void TryAim(PointerEventData e) {
if(canShoot) {
direction = initialTouchPos - e.position;
float mag = (initialTouchPos - e.position).magnitude;
float scale = Mathf.Clamp(mag/maxMagnitude, scaleThreshold, 1f);
angle = Mathf.Atan2(direction.y,direction.x) * Mathf.Rad2Deg + 90f;
aim.transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
aim.transform.localScale = Vector3.one * scale;
aim.SetActive(e.position.y < initialTouchPos.y);
}
}
public void TryShoot(PointerEventData e) {
if(canShoot && aim.activeInHierarchy) {
canShoot = false;
StartCoroutine(ShootRoutine(balls));
}
aim.SetActive(false);
}
private IEnumerator ShootRoutine(int b) {
// write code to start shooting balls.
}
Then connect the touch events with the methods like this:
1. OnPointerClick => TryBeginAim
2. OnPointerDown => TryAim
3. OnPointerUp => TryShoot
That's All !!
I hope this helps to solve this issue. Enjoy!
Related
I've added a stamina system in my game, but when I try do call the function there's a few problems happening:
I can't jump when my character is sprinting and when I jump and press the sprint button my character doesn't fall anymore, he basically just flies.
PlayerController
private Vector3 playerVelocity;
private bool groundedPlayer;
private CharacterController controller;
private PlayerControls playerControls;
private InputManager inputManager;
public HealthBar healthBar;
public StaminaBar staminaBar;
public int currentHealth;
public int maxHealth = 100;
public int currentStamina;
public int maxStamina = 100;
public int staminaDrain = 10;
[SerializeField]
private float playerSpeed = 2.0f;
[SerializeField]
private float playerRunSpeed= 1f;
[SerializeField]
private float jumpHeight = 1.0f;
[SerializeField]
private float gravityValue = -9.81f;
private Transform cameraTransform;
private void Start()
{
currentHealth = maxHealth;
healthBar.SetMaxHealth(maxHealth);
currentStamina = maxStamina;
staminaBar.SetMaxStamina(maxStamina);
controller = GetComponent<CharacterController>();
inputManager = InputManager.Instance;
cameraTransform = Camera.main.transform;
//player = GameObject.Find("Player");
}
void Update()
{
groundedPlayer = controller.isGrounded;
if (groundedPlayer && playerVelocity.y < 0)
{
playerVelocity.y = 0f;
}
Vector2 movement = inputManager.GetPlayerMovement();
Vector3 move = new Vector3(movement.x, 0f, movement.y);
move = cameraTransform.forward * move.z + cameraTransform.right * move.x;
move.y = 0f;
//controller.Move(move * Time.deltaTime * playerSpeed);
if(inputManager.isRunning && currentStamina > 0)
{
controller.Move(move * playerRunSpeed * Time.deltaTime);
staminaBar.UseStamina(staminaDrain);
staminaBar.staminaSlider.value = currentStamina;
}
else
{
controller.Move(move * Time.deltaTime * playerSpeed);
}
// Changes the height position of the player..
if (inputManager.PlayerJumpedThisFrame() && groundedPlayer)
{
playerVelocity.y += Mathf.Sqrt(jumpHeight * -3.0f * gravityValue);
}
playerVelocity.y += gravityValue * Time.deltaTime;
controller.Move(playerVelocity * Time.deltaTime);
}
StaminaBar script
public class StaminaBar : MonoBehaviour
{
public Slider staminaSlider;
private PlayerController playerController;
private WaitForSeconds regenTick = new WaitForSeconds(0.1f);
private Coroutine regen;
public void SetMaxStamina(int stamina){
staminaSlider.maxValue = stamina;
staminaSlider.value = stamina;
}
public void SetStamina(int stamina){
staminaSlider.value = stamina;
}
public void UseStamina(int amount){
if(playerController.currentStamina - amount >= 0){
playerController.currentStamina -= amount;
staminaSlider.value = playerController.currentStamina;
Debug.Log("Losing Stamina");
if(regen != null)
StopCoroutine(regen);
regen = StartCoroutine(RegenStamina());
}
else
{
Debug.Log("NotEnoughStamina");
}
}
private IEnumerator RegenStamina()
{
yield return new WaitForSeconds(2);
while(playerController.currentStamina < playerController.maxStamina){
playerController.currentStamina += playerController.maxStamina/100;
staminaSlider.value = playerController.currentStamina;
yield return regenTick;
}
regen = null;
}
}
Input Manager
{
private StaminaBar staminaBar;
private PlayerController playerController;
[SerializeField]
private float bulletHitMissDistance = 25f;
[SerializeField]
private Transform bulletParent;
[SerializeField]
private Transform barrelTransform;
[SerializeField]
private GameObject bulletPrefab;
[SerializeField]
private float damage = 100;
public float impactForce = 30;
public float fireRate = 8f;
WaitForSeconds rapidFireWait;
public bool isRunning;
private static InputManager _instance;
public static InputManager Instance
{
get {
return _instance;
}
}
private PlayerControls playerControls;
private Transform cameraTransform;
Coroutine fireCoroutine;
private void Awake()
{
if(_instance != null && _instance != this)
{
Destroy(this.gameObject);
}
else
{
_instance = this;
}
playerControls = new PlayerControls();
//Cursor.visible = false;
rapidFireWait = new WaitForSeconds(1/fireRate);
cameraTransform = Camera.main.transform;
playerControls.Player.RunStart.performed += x => Running();
playerControls.Player.RunEnd.performed += x => RunningStop();
playerControls.Player.Shoot.started += _ => StartFiring();
playerControls.Player.Shoot.canceled += _ => StopFiring();
}
private void OnEnable()
{
playerControls.Enable();
//playerControls.Player.Shoot.performed += _ => StartFiring();
}
private void OnDisable()
{
playerControls.Disable();
//playerControls.Player.Shoot.performed += _ => StopFiring();
}
void StartFiring()
{
fireCoroutine = StartCoroutine(RapidFire());
}
void StopFiring()
{
if(fireCoroutine != null)
{
StopCoroutine(fireCoroutine);
}
}
public Vector2 GetPlayerMovement()
{
return playerControls.Player.Movement.ReadValue<Vector2>();
}
public Vector2 GetMouseDelta(){
return playerControls.Player.Look.ReadValue<Vector2>();
}
public bool PlayerJumpedThisFrame(){
return playerControls.Player.Jump.triggered;
}
public void Shooting()
{
RaycastHit hit;
//creates the bullet
GameObject bullet = GameObject.Instantiate(bulletPrefab, barrelTransform.position, Quaternion.identity, bulletParent);
BulletController bulletController = bullet.GetComponent<BulletController>();
//shoots the bullet forwards
if (Physics.Raycast(cameraTransform.position, cameraTransform.forward, out hit, Mathf.Infinity))
{
//checks if the bullet hit something
bulletController.target = hit.point;
bulletController.hit = true;
//makes enemy take damage
Enemy takingDamage = hit.transform.GetComponent<Enemy>();
if (takingDamage != null)
{
takingDamage.TakeDamage(damage);
}
//makes enemy go backwards when hit
if(hit.rigidbody != null)
{
hit.rigidbody.AddForce(-hit.normal * impactForce);
}
}
else
{
bulletController.target = cameraTransform.position + cameraTransform.forward * bulletHitMissDistance;
bulletController.hit = false;
}
}
public IEnumerator RapidFire()
{
while(true)
{
Shooting();
yield return rapidFireWait;
}
}
public void Running()
{
/* if(playerController.currentStamina > 0){
isRunning = true;
staminaBar.UseStamina(playerController.staminaDrain);
staminaBar.staminaSlider.value = playerController.currentStamina;
} */
isRunning = true;
}
public void RunningStop(){
isRunning =false;
}
}
I'm using unity new input system and tried to call the function in two different ways: in the isRunning and when I actually do the sprint function.
I was expecting the player to lose 10 stamina every time I press the sprint button, I was trying to figure that out before trying to make him lose stamina while the button is pressed.
I've seen a couple videos on YouTube, which is where I got the code from, but can't find out what I'm doing wrong when calling the function, I've had similar problems before when trying to call a TakeDamage function but I guess that's a different question.
So here is what I would do.
Instead of controlling the stamina in multiple places and hve forth and back references (=dependencies) between all your scripts I would rather keep this authority within the PlayerController.
Your StaminaBar component should be purely listening and visualizing the current value without having the authority to modify it.
Next step would be to decide for a general code structure
Who is responsible for what?
Who knows / controls what?
There are many possible answers to those but for now an this specific case
You can either say the PlayerController "knows" the StaminaBar just like it also knows the InputManager and can't live without both
Or you could decouple them and let the PlayerController work without having the visualization via the StaminaBar but rather let the StaminaBar listen to the value and just display it .. or not if you want to remove or change this later on
Personally I would go with the second so I will try and give you an example how I would deal with this:
public class PlayerController : MonoBehaviour
{
[Header("Own References")]
[SerializeField] private CharacterController _controller;
[Header("Scene References")]
[SerializeField] private Transform _cameraTransform;
[SerializeField] private InputManager _inputManager;
// In general always make you stuff as encapsulated as possible
// -> nobody should be able to change these except you via the Inspector
// (Values you are anyway not gonna change at all you could also convert to "const")
[Header("Settings")]
[SerializeField] private float _maxHealth = 100f;
[SerializeField] private float _maxStamina = 100f;
[SerializeField] private float _staminaDrainPerSecond = 2f;
[SerializeField] private float _secondsDelayBeforeStaminaRegen = 1f;
[SerializeField] private float _staminaRegenPerSecond = 2f;
[SerializeField] private float _playerSpeed = 1f;
[SerializeField] private float _playerRunSpeed = 2f;
[SerializeField] private float _jumpHeight = 1f;
[SerializeField] private float _gravityValue = -9.81f;
// Your runtime valus
private float _staminaRegenDelayTimer;
private float _currentHealt;
private float _currentStamina;
// You only need a single float for this
private float _currentYVelocity;
// EVENTS we expose so other classes can react to those
public UnityEvent OnDeath;
public UnityEvent<float> OnHealthChanged;
public UnityEvent<float> OnStaminaChanged;
// Provide public read-only access to the settings so your visuals can access those for their setup
public float MaxHealth => _maxHealth;
public float MaxStamina => _maxStamina;
// And then use properties for your runtime values
// whenever you set the value you do additional stuff like cleaning the value and invoke according events
public float currentHealth
{
get => _currentHealt;
private set
{
_currentHealt = Mathf.Clamp(value, 0, _maxHealth);
OnHealthChanged.Invoke(_currentHealt);
if (value <= 0f)
{
OnDeath.Invoke();
}
}
}
public float currentStamina
{
get => _currentStamina;
private set
{
_currentStamina = Mathf.Clamp(value, 0, _maxStamina);
OnStaminaChanged.Invoke(_currentStamina);
}
}
private void Awake()
{
// As a thumb rule to avoid issues with order I usually initialize everything I an in Awake
if (!_controller) _controller = GetComponent<CharacterController>();
currentHealth = MaxHealth;
currentStamina = MaxStamina;
}
private void Start()
{
// in start do the things were you depend on others already being initialized
if (!_inputManager) _inputManager = InputManager.Instance;
if (!_cameraTransform) _cameraTransform = Camera.main.transform;
}
private void Update()
{
UpdateStamina();
UpdateHorizontalMovement();
UpdateVerticalMovement();
}
private void UpdateStamina()
{
if (_inputManager.IsRunning)
{
// drain your stamina -> also informs all listeners
currentStamina -= _staminaDrainPerSecond * Time.deltaTime;
// reset the regen timer
_staminaRegenDelayTimer = _secondsDelayBeforeStaminaRegen;
}
else
{
// only if not pressing run start the regen timer
if (_staminaRegenDelayTimer > 0)
{
_staminaRegenDelayTimer -= Time.deltaTime;
}
else
{
// once timer is finished start regen
currentStamina += _staminaRegenPerSecond * Time.deltaTime;
}
}
}
private void UpdateHorizontalMovement()
{
var movement = _inputManager.PlayerMovement;
var move = _cameraTransform.forward * movement.y + _cameraTransform.right * movement.x;
move.y = 0f;
move *= _inputManager.IsRunning && currentStamina > 0 ? _playerRunSpeed : _playerSpeed;
_controller.Move(move * Time.deltaTime);
}
private void UpdateVerticalMovement()
{
if (_controller.isGrounded)
{
if (_inputManager.JumpedThisFrame)
{
_currentYVelocity += Mathf.Sqrt(_jumpHeight * -3.0f * _gravityValue);
}
else if (_currentYVelocity < 0)
{
_currentYVelocity = 0f;
}
}
else
{
_currentYVelocity += _gravityValue * Time.deltaTime;
}
_controller.Move(Vector3.up * _currentYVelocity * Time.deltaTime);
}
}
And then your StaminaBar shinks down to really only being a display. The PlayerController doesn't care/even know it exists and can fully work without it.
public class StaminaBar : MonoBehaviour
{
[SerializeField] private Slider _staminaSlider;
[SerializeField] private PlayerController _playerController;
private void Awake()
{
// or wherever you get the reference from
if (!_playerController) _playerController = FindObjectOfType<PlayerController>();
// poll the setting from the player
_staminaSlider.maxValue = _playerController.MaxStamina;
// attach a callback to the event
_playerController.OnStaminaChanged.AddListener(OnStaminaChanged);
// just to be sure invoke the callback once immediately with the current value
// so we don't have to wait for the first actual event invocation
OnStaminaChanged(_playerController.currentStamina);
}
private void OnDestroy()
{
if(_playerController) _playerController.OnStaminaChanged.RemoveListener(OnStaminaChanged);
}
// This will now be called whenever the stamina has changed
private void OnStaminaChanged(float stamina)
{
_staminaSlider.value = stamina;
}
}
And just for completeness - I also refactored your InputManager a bit on the fly ^^
public class InputManager : MonoBehaviour
{
[Header("Own references")]
[SerializeField] private Transform _bulletParent;
[SerializeField] private Transform _barrelTransform;
[Header("Scene references")]
[SerializeField] private Transform _cameraTransform;
// By using the correct component right away you can later skip "GetComponent"
[Header("Assets")]
[SerializeField] private BulletController _bulletPrefab;
[Header("Settings")]
[SerializeField] private float _bulletHitMissDistance = 25f;
[SerializeField] private float _damage = 100;
[SerializeField] private float _impactForce = 30;
[SerializeField] private float _fireRate = 8f;
public static InputManager Instance { get; private set; }
// Again I would use properties here
// You don't want anything else to set the "isRunning" flag
// And the others don't need to be methods either
public bool IsRunning { get; private set; }
public Vector2 PlayerMovement => _playerControls.Player.Movement.ReadValue<Vector2>();
public Vector2 MouseDelta => _playerControls.Player.Look.ReadValue<Vector2>();
public bool JumpedThisFrame => _playerControls.Player.Jump.triggered;
private Coroutine _fireCoroutine;
private PlayerControls _playerControls;
private WaitForSeconds _rapidFireWait;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
}
else
{
Instance = this;
}
_playerControls = new PlayerControls();
//Cursor.visible = false;
_rapidFireWait = new WaitForSeconds(1 / _fireRate);
_cameraTransform = Camera.main.transform;
_playerControls.Player.RunStart.performed += _ => Running();
_playerControls.Player.RunEnd.performed += _ => RunningStop();
_playerControls.Player.Shoot.started += _ => StartFiring();
_playerControls.Player.Shoot.canceled += _ => StopFiring();
}
private void OnEnable()
{
_playerControls.Enable();
}
private void OnDisable()
{
_playerControls.Disable();
}
private void StartFiring()
{
_fireCoroutine = StartCoroutine(RapidFire());
}
private void StopFiring()
{
if (_fireCoroutine != null)
{
StopCoroutine(_fireCoroutine);
_fireCoroutine = null;
}
}
private void Shooting()
{
var bulletController = Instantiate(_bulletPrefab, _barrelTransform.position, Quaternion.identity, _bulletParent);
if (Physics.Raycast(_cameraTransform.position, _cameraTransform.forward, out var hit, Mathf.Infinity))
{
bulletController.target = hit.point;
bulletController.hit = true;
if (hit.transform.TryGetComponent<Enemy>(out var enemy))
{
enemy.TakeDamage(_damage);
}
if (hit.rigidbody != null)
{
hit.rigidbody.AddForce(-hit.normal * _impactForce);
}
}
else
{
bulletController.target = _cameraTransform.position + _cameraTransform.forward * _bulletHitMissDistance;
bulletController.hit = false;
}
}
private IEnumerator RapidFire()
{
while (true)
{
Shooting();
yield return _rapidFireWait;
}
}
private void Running()
{
IsRunning = true;
}
private void RunningStop()
{
IsRunning = false;
}
}
You're decreasing and increasing the stamina in the same scope. I think you should let the stamina to be drained when sprint is pressed and start regenerating only if it is released.
I'm working on a portal system and am up to making the traveller go through the portal. For this, I just set the traveller's position to the position of the other portal. When I do this, however, the Z rotation of the camera gets skewed, and they are teleported slightly higher than they should. Any help is welcome <3
Portal Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Portal : MonoBehaviour
{
//Portal Camera Positioning
public Portal linkedPortal;
private Camera portalCam;
private Camera playerCam;
//Portal Sceen Texture
private PortalScreen portalScreen;
private MeshRenderer screenRen;
private RenderTexture viewTexture;
//Travellers
//[SerializableField]
List<PortalTraveller> trackedTravellers;
public Vector3 desiredCameraRotation;
public Vector3 desiredCameraPosition;
private Transform globalRuler;
PortalTraveller[] travellersToAdd;
[HideInInspector]
public float travellerDistance;
//Debug
[HideInInspector]
public float dstToPlayer;
void Awake()
{
playerCam = Camera.main;
portalCam = linkedPortal.GetComponentInChildren<Camera>();
portalScreen = GetComponentInChildren<PortalScreen>();
screenRen = portalScreen.GetComponent<MeshRenderer>();
globalRuler = GameObject.FindWithTag("GlobalRuler").transform;
trackedTravellers = new List<PortalTraveller>();
travellersToAdd = GameObject.FindObjectsOfType<PortalTraveller>();
foreach (PortalTraveller traveller in travellersToAdd)
{
if (!trackedTravellers.Contains(traveller))
{
trackedTravellers.Add(traveller);
}
}
}
void Update()
{
Render();
}
static bool VisibleFromCamera(Renderer renderer, Camera camera)
{
Plane[] frustumPlanes = GeometryUtility.CalculateFrustumPlanes(camera);
return GeometryUtility.TestPlanesAABB(frustumPlanes, renderer.bounds);
}
void Render()
{
if (!VisibleFromCamera(screenRen, playerCam))
{
return;
}
desiredCameraPosition = transform.position - (playerCam.transform.position - linkedPortal.portalScreen.screenPos);
desiredCameraPosition.y = playerCam.transform.position.y;
desiredCameraRotation = playerCam.transform.eulerAngles + new Vector3 (0f, 180f, 0f);
portalCam.transform.position = desiredCameraPosition;
portalCam.transform.eulerAngles = desiredCameraRotation;
if (viewTexture == null || viewTexture.width != Screen.width || viewTexture.height != Screen.height)
{
if (viewTexture != null)
{
viewTexture.Release();
}
viewTexture = new RenderTexture (Screen.width, Screen.height, 0);
portalCam.targetTexture = viewTexture;
screenRen.material.SetTexture("_MainTex", viewTexture);
}
}
void LateUpdate()
{
HandleTravellers();
}
void HandleTravellers()
{
for (int i = 0; i < trackedTravellers.Count; i++)
{
PortalTraveller traveller = trackedTravellers[i];
Transform travellerT = trackedTravellers[i].transform;
Vector3 toTraveller = traveller.transform.position - transform.position;
int portalSide = System.Math.Sign(Vector3.Dot(toTraveller, transform.right));
int portalSideOld = System.Math.Sign(Vector3.Dot(traveller.previousTravellerVector, transform.right));
travellerDistance = Mathf.Abs(Vector3.Distance(transform.position, travellerT.position));
//Debug.Log("Handled");
if (travellerDistance > linkedPortal.travellerDistance)
{
break;
}
if (portalSide < portalSideOld && travellerDistance < globalRuler.transform.lossyScale.z / 2 )
{
//Debug.Log(travellerDistance);
var positionOld = travellerT.position;
var rotationOld = travellerT.rotation;
Quaternion rot = Quaternion.Euler(desiredCameraRotation.x, desiredCameraRotation.y, 0f);
traveller.Teleport(transform, linkedPortal.transform, desiredCamerxaPosition, rot);
traveller.previousTravellerVector = toTraveller;
//trackedTravellers.RemoveAt(i);
i--;
}
else
{
traveller.previousTravellerVector = toTraveller;
dstToPlayer = Mathf.Abs(Vector3.Distance(transform.position, travellerT.position));
}
}
}
void OnValidate()
{
if (linkedPortal != null)
{
linkedPortal.linkedPortal = this;
}
travellersToAdd = GameObject.FindObjectsOfType<PortalTraveller>();
}
}
Traveller Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PortalTraveller : MonoBehaviour
{
public Vector3 previousTravellerVector;
public void Teleport(Transform originalPortal, Transform linkedPortal, Vector3 pos, Quaternion rot)
{
transform.position = pos;
transform.rotation = rot;
}
}
Alright so basically the issue that I've been having is that for some reason a GameObject is interfering with the OnPointerEnter function. I'm pretty sure that OnPointerEnter detects only UI. So that's why I'm extremely confused when seeing that a specific GameObject in this case the PlayerLogic GameObject (which you can see in the screenshot) is for some reason interfering with the detection of UI elements. The reason I believe it is this specific GameObject is because once I do PlayerLogic.SetActive(false); OnPointerEnter starts to work again, and I'm also sure that it isn't any of the children of PlayerLogic because I've tried turning them off specifically and it still didn't work.
Inspector of the PlayerLogic object
Hierarchy
The code I'm using to test OnPointerEnter
After some testing I've realized that its the specific issue lies within the Player script on the PlayerLogic GameObject. Now what confuses me is that once I turn off the Player component OnPointer doesn't work, but if I were to remove the Player component completely from the PlayerLogic GameObject OnPointerEnter works.
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System.Collections;
using System.Collections.Generic;
public class Player : MonoBehaviour, TakeDamage {
[SerializeField] private Animator playerAnimator;
[SerializeField] private Transform mainCameraTransform;
private bool isRunning = false;
[SerializeField] private CharacterController controller;
public float speed = 10f;
[SerializeField] private float jumpForce = 3f;
[SerializeField] private float gravity = -10000.81f;
Vector3 velocity;
Vector3 desiredMoveDirection;
private float dashSpeed = 30f;
private float mouseX;
private float mouseY;
[SerializeField]
private Transform Target;
[SerializeField]
private Transform player;
private float turnSmoothVelocity;
private float time = 0f;
public bool playerIsAttacking = false;
[SerializeField] private Slider playerHealth, playerMana;
[SerializeField] private TextMeshProUGUI healthText, manaText;
private Vector3 originalSpawnPos;
private bool playerIsDead = false;
[SerializeField] private LayerMask enemyLayerMask;
[SerializeField] private Transform playerLook;
private ShowHPBar obj;
private bool HPBarShown = false;
private bool unshowingHPBar = false;
public bool lookingAtEnemy = false;
public RaycastHit hit;
[SerializeField] private Canvas abilityCanvas;
[SerializeField] private Slider CD1;
[SerializeField] private Slider CD2;
[SerializeField] private Slider CD3;
public List<Ability> currentlyEquippedAbilites = new List<Ability>();
public List<string> abilityTexts = new List<string>();
public float[] abilityCooldowns = new float[3];
private float manaRegenTime;
//public List<Image> abilityImages = new List<Image>();
private void Awake() {
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
}
private void Start() {
playerHealth.onValueChanged.AddListener(delegate {OnValueChangedHealth(); });
playerMana.onValueChanged.AddListener(delegate {OnValueChangedMana(); });
originalSpawnPos = transform.position;
}
private void Update() {
if (!playerIsDead) {
PlayerMovementAndRotation();
}
PlayerDash();
PlayerRun();
PlayerSeeEnemyHealth();
PlayerActivateAbility();
if (manaRegenTime > 0.5f) {
playerMana.value += playerMana.maxValue/100;
manaRegenTime = 0;
}
playerLook.rotation = mainCameraTransform.rotation;
time += Time.deltaTime;
manaRegenTime += Time.deltaTime;
#region Ability Cooldowns
if (currentlyEquippedAbilites.Count > 0) {
if (currentlyEquippedAbilites[0].cooldown <= abilityCooldowns[0])
{
currentlyEquippedAbilites[0].isOnCooldown = false;
abilityCooldowns[0] = 0;
CD1.value = 0;
}
else if (currentlyEquippedAbilites[0].isOnCooldown) {
abilityCooldowns[0] += Time.deltaTime;
CD1.value = currentlyEquippedAbilites[0].cooldown - abilityCooldowns[0];
}
}
if (currentlyEquippedAbilites.Count > 1) {
if (currentlyEquippedAbilites[1].cooldown <= abilityCooldowns[1])
{
currentlyEquippedAbilites[1].isOnCooldown = false;
abilityCooldowns[1] = 0;
CD2.value = 0;
}
else if (currentlyEquippedAbilites[1].isOnCooldown) {
abilityCooldowns[1] += Time.deltaTime;
CD2.value = currentlyEquippedAbilites[1].cooldown - abilityCooldowns[1];
}
}
if (currentlyEquippedAbilites.Count > 2) {
if (currentlyEquippedAbilites[2].cooldown <= abilityCooldowns[2])
{
currentlyEquippedAbilites[2].isOnCooldown = false;
abilityCooldowns[2] = 0;
CD3.value = 0;
}
else if (currentlyEquippedAbilites[2].isOnCooldown) {
abilityCooldowns[2] += Time.deltaTime;
CD3.value = currentlyEquippedAbilites[2].cooldown - abilityCooldowns[2];
}
}
#endregion
}
private void PlayerRun() {
if (Input.GetKey(KeybindsScript.RunKey) && (Input.GetAxisRaw("Horizontal") != 0 || Input.GetAxisRaw("Vertical") != 0)) {
playerAnimator.SetInteger("isRunning", 1);
playerAnimator.SetInteger("isIdle", 0);
playerAnimator.SetInteger("isWalking", 0);
speed = 15f;
}
else if (Input.GetAxisRaw("Horizontal") != 0 || Input.GetAxisRaw("Vertical") != 0) {
playerAnimator.SetInteger("isWalking", 1);
playerAnimator.SetInteger("isIdle", 0);
playerAnimator.SetInteger("isRunning", 0);
speed = 10f;
}
else {
playerAnimator.SetInteger("isRunning", 0);
playerAnimator.SetInteger("isWalking", 0);
playerAnimator.SetInteger("isIdle", 1);
speed = 10f;
}
}
private void PlayerMovementAndRotation() {
bool isGrounded = controller.isGrounded;
if (isGrounded && velocity.y < 0) {
velocity.y = -2f;
}
float horizontal = Input.GetAxisRaw("Horizontal");
float vertical = Input.GetAxisRaw("Vertical");
Vector3 moveDir = (transform.right*horizontal+transform.forward*vertical).normalized;
controller.Move(moveDir*Time.deltaTime*speed);
transform.eulerAngles = new Vector3(0f, mainCameraTransform.eulerAngles.y, 0f);
if (Input.GetKeyDown(KeybindsScript.JumpKey) && isGrounded) {
velocity.y = Mathf.Sqrt(jumpForce * -2f * gravity);
}
velocity.y += gravity * Time.deltaTime;
controller.Move(velocity * Time.deltaTime);
}
private void PlayerDash() {
if (Input.GetKeyDown(KeybindsScript.DashKeybind) && isRunning == false) {
if (Input.GetKey(KeybindsScript.MovementKeyBackward)) {
StartCoroutine(PlayerDashTiming(2));
}
else if (Input.GetKey(KeybindsScript.MovementKeyRight)) {
StartCoroutine(PlayerDashTiming(3));
}
else if (Input.GetKey(KeybindsScript.MovementKeyLeft)) {
StartCoroutine(PlayerDashTiming(4));
}
else {
StartCoroutine(PlayerDashTiming(1));
}
}
}
private void PlayerSeeEnemyHealth() {
if (Physics.Raycast(playerLook.position, playerLook.forward, out hit, 1000f, enemyLayerMask)) {
obj = hit.transform.gameObject.GetComponent<ShowHPBar>();
if (obj != null && !HPBarShown && !unshowingHPBar) {obj.ShowHPBarFunction(); HPBarShown = true;}
lookingAtEnemy = true;
}
else {
if (obj != null && HPBarShown) {StartCoroutine(UnShowHPBar(obj)); HPBarShown = false;}
}
}
public void PlayerEquipAbility(Ability ability, int place) {
if (currentlyEquippedAbilites.Count < place) {currentlyEquippedAbilites.Add(ability);}
else {currentlyEquippedAbilites[place-1] = ability;}
//if (abilityImages.Count < place) {abilityImages.Add(ability.icon);}
//else {abilityImages.Add(ability.icon);}
if (abilityTexts.Count < place) {abilityTexts.Add(ability.name);}
else {abilityTexts[place-1] = ability.name;}
for (int i=0;i < abilityTexts.Count;++i) {
abilityCanvas.transform.GetChild(i).GetChild(0).GetComponent<TextMeshProUGUI>().text = abilityTexts[i];
abilityCanvas.transform.GetChild(i).GetChild(1).GetComponent<Slider>().maxValue = ability.cooldown;
}
}
private void PlayerActivateAbility() {
if (Input.GetKeyDown(KeyCode.Alpha1)) {
if (currentlyEquippedAbilites[0] != null) {
if (currentlyEquippedAbilites[0].manaCost <= playerMana.value && !currentlyEquippedAbilites[0].isOnCooldown) {
ParticleEffect pe = currentlyEquippedAbilites[0].script.gameObject.GetComponent<ParticleEffect>();
playerMana.value -= currentlyEquippedAbilites[0].manaCost;
currentlyEquippedAbilites[0].isOnCooldown = true;
if (pe != null) {pe.PlayAnimation();}
}
}
}
}
public void OnValueChangedHealth() {
healthText.text = playerHealth.value + "/" + PlayerStats.HealthPoints;
}
public void OnValueChangedMana() {
manaText.text = playerMana.value + "/" + PlayerStats.ManaAmount;
}
public void TakeDamageFunction(float damage) {
playerHealth.value -= damage;
if (playerHealth.value <= 0) {
StartCoroutine(PlayerDeath());
}
}
IEnumerator PlayerDashTiming(int x) {
isRunning = true;
float time = 0f;
Vector3 savedVector = Vector3.zero;
switch (x) {
case 1:
savedVector = transform.forward;
break;
case 2:
savedVector = -transform.forward;
break;
case 3:
savedVector = transform.right;
break;
case 4:
savedVector = -transform.right;
break;
}
while(time < .3f)
{
time += Time.deltaTime;
controller.Move(savedVector * dashSpeed * Time.deltaTime);
yield return null;
}
yield return new WaitForSeconds(1.5f);
isRunning = false;
}
IEnumerator PlayerDeath() {
//Respawn
playerIsDead = true;
playerAnimator.enabled = false;
yield return new WaitForSeconds(1f);
playerHealth.value = 100;
transform.position = originalSpawnPos;
yield return new WaitForSeconds(0.1f);
playerIsDead = false;
playerAnimator.enabled = true;
}
IEnumerator UnShowHPBar(ShowHPBar obj) {
unshowingHPBar = true;
yield return new WaitForSeconds(1.5f);
obj.ShowHPBarFunction();
unshowingHPBar = false;
}
}
This is the Player.cs script.
I'm pretty sure that OnPointerEnter detects only UI.
No.
It works "out of the box" on UI because by default every Canvas contains a GraphicsRaycaster component which is then used and handled by the EventSystem.
For non-UI 3D objects you have to make sure
your objects have 3D Colliders
the Colliders are on the same object as the IPointerXY interfaces
on your Camera there is a PhysicsRayster component
For non-UI 2D objects quite similar
your objects have a 2D Collider
the colliders are on the same object as the IPointerXY interfaces
the Camera has a Physics2DRaycaster component
Note that it is possible that any other collider or in general raycast blocking object is between your input and the target object which would also prevent the OnPointerXY to be triggered on your objects.
The CharacterController
is simply a capsule shaped Collider
which can be told to move in some direction from a script.
which is probably blocking the input.
Now with your Player code:
You do
Cursor.lockState = CursorLockMode.Locked;
in Awake so even if you turn it off afterwards this already took effect.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MoveOnCurvedLine : MonoBehaviour
{
public LineRenderer lineRenderer;
public GameObject objectToMove;
public float speed;
public bool go = false;
public bool moveToFirstPositionOnStart = false;
private Vector3[] positions;
private Vector3[] pos;
private int index = 0;
// Start is called before the first frame update
void Start()
{
pos = GetLinePointsInWorldSpace();
if (moveToFirstPositionOnStart == true)
{
objectToMove.transform.position = pos[index];
}
}
Vector3[] GetLinePointsInWorldSpace()
{
positions = new Vector3[lineRenderer.positionCount];
//Get the positions which are shown in the inspector
lineRenderer.GetPositions(positions);
//the points returned are in world space
return positions;
}
// Update is called once per frame
void Update()
{
if (go == true)
{
Move();
}
}
void Move()
{
objectToMove.transform.position = Vector3.MoveTowards(objectToMove.transform.position,
pos[index],
speed * Time.deltaTime);
if (objectToMove.transform.position == pos[index])
{
index += 1;
}
if (index == pos.Length)
index = 0;
}
}
Even if I'm setting the speed value to 1000 or even to 10000 the speed is faster but not so fast or not very fast.
There are 1157 position in the variable array positions. So it's a bit a problem to move fast no so many positions and in other cases there might be 5000 positions or more depending on the linerenderer line length.
but I saw in some youtube demos cars and objects moving fast very fast on curved lines or very long length lines.
Figure out how much distance to move in the current frame, then loop through points until you've traveled that distance:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MoveOnCurvedLine : MonoBehaviour
{
public LineRenderer lineRenderer;
public GameObject objectToMove;
public float speed;
public bool go = false;
public bool moveToFirstPositionOnStart = false;
private Vector3[] positions;
private Vector3[] pos;
private int index = 0;
// Start is called before the first frame update
void Start()
{
pos = GetLinePointsInWorldSpace();
if (moveToFirstPositionOnStart == true)
{
objectToMove.transform.position = pos[index];
}
}
Vector3[] GetLinePointsInWorldSpace()
{
positions = new Vector3[lineRenderer.positionCount];
//Get the positions which are shown in the inspector
lineRenderer.GetPositions(positions);
//the points returned are in world space
return positions;
}
// Update is called once per frame
void Update()
{
if (go == true)
{
Move();
}
}
void Move()
{
Vector3 newPos = objectToMove.transform.position;
float distanceToTravel = speed * Time.deltaTime;
bool stillTraveling = true;
while (stillTraveling)
{
Vector3 oldPos = newPos;
newPos = Vector3.MoveTowards(oldPos, pos[index], distanceToTravel);
distanceToTravel -= Vector3.Distance(newPos, oldPos);
if (newPos == pos[index]) // Vector3 comparison is approximate so this is ok
{
index = (index + 1) % pos.Length;
}
else
{
stillTraveling = false;
}
}
objectToMove.transform.position = newPos;
}
}
I am doing a project in Unity with C# and I want to have two drones that will wander in the room, and then attack each other using a state machine.
I have three classes AttackState, ChaseState and WonderState
For example, this is my WanderState Class:
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Linq;
using System;
using UnityEngine;
public class WanderState : BaseState
{
private Vector3? _destination;
private float stopDistance = 1.5f;
private float turnSpeed = 1f;
private readonly LayerMask _layerMask = LayerMask.NameToLayer("Walls");
private float _rayDistance = 5.0f;
private Quaternion _desiredRotation;
private Vector3 _direction;
private Drone _drone;
public WanderState(Drone drone) : base(drone.gameObject)
{
_drone = drone;
}
public override Type Tick()
{
var chaseTarget = CheckForAggro();
if (chaseTarget != null)
{
_drone.SetTarget(chaseTarget);
return typeof(ChaseState);
}
if (_destination.HasValue == false || Vector3.Distance(transform.position, _destination.Value) <= stopDistance)
{
FindRandomDestination();
}
transform.rotation = Quaternion.Slerp(transform.rotation, _desiredRotation, Time.deltaTime * turnSpeed); //Time.deltaTime * turnSpeed
if (IsForwardBlocked()) //IsForwardBlocked()
{
transform.rotation = Quaternion.Lerp(transform.rotation, _desiredRotation, 0.2f);
}
else
{
float droneSpeed = 2f;
transform.Translate(Vector3.forward * Time.deltaTime * droneSpeed);
}
Debug.DrawRay(transform.position, _direction * _rayDistance, Color.red);
while (IsPathBlocked())
{
FindRandomDestination();
Debug.Log("Wall");
}
return null;
}
private bool IsForwardBlocked()
{
Ray ray = new Ray(transform.position, transform.forward);
return Physics.SphereCast(ray, 0.5f, _rayDistance, _layerMask);
}
private bool IsPathBlocked()
{
Rigidbody obj = new Rigidbody();
Ray ray = new Ray(transform.position, _direction);
return Physics.SphereCast(ray, 0.5f, _rayDistance, _layerMask);
}
private void FindRandomDestination()
{
Vector3 testPosition = (transform.position + (transform.forward * 4f)) + new Vector3(UnityEngine.Random.Range(-4.5f, 4.5f), 0f,UnityEngine.Random.Range(-4.5f, 4.5f));
_destination = new Vector3(testPosition.x, 1f, testPosition.z);
_direction = Vector3.Normalize(_destination.Value - transform.position);
_direction = new Vector3(_direction.x, 0f, _direction.z);
_desiredRotation = Quaternion.LookRotation(_direction);
Debug.Log("Got direction");
}
Quaternion startingAngle = Quaternion.AngleAxis(-60, Vector3.up);
Quaternion stepAngle = Quaternion.AngleAxis(5, Vector3.up);
private Transform CheckForAggro()
{
float aggroRadius = 5f;
RaycastHit hit;
var angle = transform.rotation * startingAngle;
var direction = angle * Vector3.forward;
var pos = transform.position;
for (var i = 0; i < 24; i++)
{
if (Physics.Raycast(pos, direction, out hit, aggroRadius))
{
var drone = hit.collider.GetComponent<Drone>();
if (drone != null && drone.Team1 != gameObject.GetComponent<Drone>().Team1)
{
Debug.DrawRay(pos, direction * hit.distance, Color.red);
return drone.transform;
}
else
{
Debug.DrawRay(pos, direction * hit.distance, Color.yellow);
}
}
else
{
Debug.DrawRay(pos, direction * aggroRadius, Color.white);
}
direction = stepAngle * direction;
}
return null;
}
}
Then I have 2 drones, and each of them have a Drone script and a StateMachine script as follows:
public class Drone : MonoBehaviour
{
[SerializeField] private Team _team;
[SerializeField] private GameObject _laserVisual;
public Transform Target { get; private set; }
public Team Team1=> _team;
public StateMachine StateMachine => GetComponent<StateMachine>();
private void Awake()
{
InitializeStateMachine();
}
private void InitializeStateMachine()
{
var states = new Dictionary<Type, BaseState>()
{
{typeof(WanderState), new WanderState(this) },
{typeof(ChaseState), new ChaseState(this) },
{typeof(AttackState), new AttackState(this) }
};
GetComponent<StateMachine>().SetStates(states);
}
public void SetTarget(Transform target)
{
Target = Target;
}
public void FireWeapon()
{
_laserVisual.transform.position = (Target.position + transform.position) / 2f;
float distance = Vector3.Distance(a: Target.position, b: transform.position);
_laserVisual.transform.localScale = new Vector3(0.1f, 0.1f, distance);
_laserVisual.SetActive(true);
StartCoroutine(TurnOffLaser());
}
public IEnumerator TurnOffLaser()
{
yield return new WaitForSeconds(0.25f);
_laserVisual.SetActive(false);
if (Target != null)
{
GameObject.Destroy(Target.gameObject);
}
}
public enum Team
{
Red,
Blue
}
public class StateMachine : MonoBehaviour
{
private Dictionary<Type, BaseState> _availableStates;
public BaseState CurrentState { get; private set; }
public event Action<BaseState> OnStateChanged;
public void SetStates(Dictionary<Type, BaseState> states)
{
_availableStates = states;
}
private void Update()
{
if(CurrentState == null)
{
CurrentState = _availableStates.Values.First();
}
var nextState = CurrentState?.Tick();
if (nextState != null && nextState != CurrentState.GetType())
{
SwitchToNewState(nextState);
}
}
public void SwitchToNewState(Type nextState)
{
CurrentState = _availableStates[nextState];
OnStateChanged?.Invoke(CurrentState);
}
}
The problem I am facing is that my drones are going through the room walls
I tried to set for the walls a Mesh Collider or a Box collider, but none of these options worked. Also, for the drones I have a sphere collider.
Does anyone know why this behaviour and what can I do to fix it?
Add a rigidbody to the drone and make sure isKinematic is unchecked.