For my grid-based game I want the enemies to have a line of sight that is visible to the player. The enemies can only look in one direction (up, down, left or right) and they won't change that direction once set.
The enemies cannot look through Obstacles or other Enemies, which is why I have set up a Raycast. This Raycast detects if the Player is in a direct line to the direction of the Enemy.
This is my EnemySight Scrip:
PathFollower pathFollower;
public GameObject sightline;
public GameObject player;
float rayLength = 20f;
bool onSameZAxis = false;
Ray myRay;
RaycastHit hit;
void Start()
{
GameObject g = GameObject.Find("Path");
pathFollower = g.GetComponent<PathFollower>();
}
void Update()
{
DrawRay();
if (player.transform.position.z.Equals(transform.position.z) && pathFollower.hasCheckedAxis == false)
{
onSameZAxis = true;
}
else { onSameZAxis = false; }
//checks only when enemy stands still if the player is in sight and on same axis
if (onSameZAxis == true && pathFollower.standsStill == true && PlayerInSight() == true)
{
Debug.Log("spotted");
pathFollower.sawPlayer = true;
pathFollower.hasCheckedAxis = true;
//onSameZAxis = false;
//Debug.Log("In the If-Loop");
}
}
//checks if there is a wall between the player and the enemy
bool PlayerInSight()
{
myRay = new Ray(transform.position + new Vector3(0, 0.15f, 0), -transform.right);
Debug.DrawRay(myRay.origin, myRay.direction, Color.red);
if (Physics.Raycast(myRay, out hit, rayLength))
{
if (hit.collider.tag == "Player")
{
return true;
}
}
return false;
}
void DrawRay()
{
//Moves the Sightline Object.
}
}
I have made the Sightline GameObject a Child of the respective enemy, so it moves with the enemy. The problem is that it will move through Obstacles and Enemies, altough the player won't be detected then.
I was thinking about moving the pivot point of my Sightline GameObject to the Vector 3 hit of the Raycast but I don't know how that could work.
I'm happy about any suggestions or solutions, thank you in advance!
Related
I am using an overlap box to detect whether an arrow hits the ground or an enemy, the arrow hitting the enemy works, it's that often the arrow will just pass through the ground, not sure if I am missing something.
Heres my code for detecting the ground or enemy.
private void FixedUpdate()
{
damageHit = Physics2D.OverlapBox(damagePosition.position, damageSize, whatIsEnemy);
groundHit = Physics2D.OverlapBox(damagePosition.position, damageSize, whatIsGround);
if (!hasHitGround)
{
if (damageHit)
{
Collider2D[] detectedObjects = Physics2D.OverlapBoxAll(damagePosition.position, damageSize, whatIsEnemy);
foreach (Collider2D collider in detectedObjects)
{
IDamageable damageable = collider.GetComponent<IDamageable>();
if (damageable != null)
{
damageable.Damage(weaponData.attackDamage);
Destroy(gameObject);
}
IKnockbackable knockbackable = collider.GetComponent<IKnockbackable>();
if (knockbackable != null)
{
knockbackable.KnockBack(weaponData.knockbackAngle, weaponData.knockbackStrength, (int)Mathf.Sign(transform.rotation.y));
}
}
}
if (groundHit)
{
rb.gravityScale = 0f;
rb.velocity = Vector2.zero;
hasHitGround = true;
Destroy(gameObject, 10f);
}
else if (Mathf.Abs(xStartPos - transform.position.x) >= travelDistance && !isGravityOn)
{
isGravityOn = true;
rb.gravityScale = gravity;
}
}
}
private void OnDrawGizmos()
{
Gizmos.DrawWireCube(damagePosition.position, damageSize) ;
}
I thought that the hitbox may have been to small and it would just pass through wall due to not hitting it on any frame. After making the hitbox larger it still did not help, here you can see that even though the overlap box is overlaping the collider the arrow will continue on.
I'm developing a 3d FPS game in Unity. At the moment I'm implementing a wall-running mechanic. The logic behind this is very simple - if player is pushing forward, and not grounded, and touching a wall, I constrain the Y/Z direction (but player can still run forward as I ignore the X direction though) and turn off gravity. It seems to work fine, a little bit clumsy but ok for me. Except, when the wall is left behind player is still able to run in mid-air until he runs out of inertia (here's the example: https://imgur.com/a/LtbWs9J). Here's the code:
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(AudioSource))]
public class WallRunning : MonoBehaviour
{
public AudioClip audioClip;
private CharacterMovement cm;
private Rigidbody rb;
private bool isJumping;
public bool isWall;
private bool playAudio;
private AudioSource audioSource;
public float energyLimit = 3.5f;
private void Start()
{
//Get attached components so we can interact with them in our script.
cm = GetComponent<CharacterMovement>();
rb = GetComponent<Rigidbody>();
audioSource = GetComponent<AudioSource>();
}
private void FixedUpdate()
{
bool jumpPressed = Input.GetButtonDown("Jump");
float verticalAxis = Input.GetAxis("Vertical");
//Check if the controller is grounded.
if (cm.Grounded)
{
isJumping = false;
isWall = false;
}
//Has the jump button been pressed.
if (jumpPressed)
{
StartCoroutine(Jumping());
}
//If we are pushing forward, and not grounded, and touching a wall.
if (verticalAxis > 0 && isJumping && isWall)
{
StartCoroutine(Energy());
//We constrain the Y/Z direction to defy gravity and move off the wall.
//But we can still run forward as we ignore the X direction.
rb.useGravity = false;
rb.constraints = RigidbodyConstraints.FreezePositionY | RigidbodyConstraints.FreezePositionX | RigidbodyConstraints.FreezeRotation;
//We also telegraph to the player by playing a sound effect on contact.
if (audioClip != null && playAudio == true)
{
audioSource.PlayOneShot(audioClip);
//We block more audio being played while we are on the wall.
playAudio = false;
}
}
else
{
StopCoroutine(Energy());
//We need to make sure we can play audio again when touching the wall.
playAudio = true;
rb.useGravity = true;
rb.constraints = RigidbodyConstraints.FreezeRotation;
}
}
void OnCollisionEnter(Collision other)
{
//Are we touching a wall object?
if (other.gameObject.CompareTag("Walls"))
{
isWall = true;
}
}
void OnCollisionExit(Collision other)
{
//Did we stop touching the wall object?
if (!other.gameObject.CompareTag("Walls"))
{
isWall = false;
}
}
IEnumerator Jumping()
{
//Check for 5 frames after the jump button is pressed.
int frameCount = 0;
while (frameCount < 5)
{
frameCount++;
//Are we airbourne in those 5 frames?
if (!cm.Grounded)
{
isJumping = true;
}
yield return null;
}
}
IEnumerator Energy()
{
yield return new WaitForSeconds(energyLimit);
isWall = false;
}
}
Notice: walls have box colliders on them ("Is Trigger" checkbox is unchecked), and player has non-kinematic rigidbody and capsule collider attached. Walls aren't marked as "static" and assigned to Default layer, while player is assigned to the Player layer.
What am I doing wrong? I'm sure I screwed up with the code, but can't figure out the problem.
Replace
void OnCollisionExit(Collision other)
{
//Did we stop touching the wall object?
if (!other.gameObject.CompareTag("Walls"))
{
isWall = false;
}
}
With
void OnCollisionExit(Collision other)
{
//Did we stop touching the wall object?
if (other.gameObject.CompareTag("Walls"))
{
isWall = false;
}
}
2 items are placed in AR. i would like to rotate 1 of those items while its in the middle of the screen (using touch controls and Raycast).
I would like all the rotation part of the code to be executed when the camera is looking at the object.
this is the script i use on an object placed in the scene, the Move function works since that is only activated when you tap on the screen where the object is and drag it away (Raycast to a Collider). however my rotation works on all objects in the scene when i swipe anywhere because if i swipe on the collider i move the object, so for rotation i have to stay outside that collider.
public class MoveObject : MonoBehaviour
{
public bool holding;
private Component rotator;
int doubleTap = 0;
void Start()
{
holding = false;
}
void Update()
{
if (holding)
{
Move();
}
// One finger
if (Input.touchCount == 1)
{
// Tap on Object
if (Input.GetTouch(0).phase == TouchPhase.Began)
{
Ray ray = Camera.main.ScreenPointToRay(Input.GetTouch(0).position);
RaycastHit hitTouch;
if (Physics.Raycast(ray, out hitTouch, 100f))
{
if (hitTouch.transform == transform)
{
holding = true;
}
}
}
// Release
if (Input.GetTouch(0).phase == TouchPhase.Ended)
{
holding = false;
}
}
if (Input.touchCount == 1 && !holding) //THIS is the rotation part
{
// GET TOUCH 0
Touch touch0 = Input.GetTouch(0);
// APPLY ROTATION
if (touch0.phase == TouchPhase.Moved)
{
transform.Rotate(0f, touch0.deltaPosition.x * 0.5f, 0f);
}
}
}
void Move()
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.GetTouch(0).position);
// The GameObject this script attached should be on layer "Surface"
if (Physics.Raycast(ray, out hit, 30.0f,
LayerMask.GetMask("Surface")))
{
transform.position = new Vector3(hit.point.x,
transform.position.y,
hit.point.z);
}
}
}
You'll probably want another flag similar to holding to make sure that you don't rotate an object the center of the screen passes over during a swipe, such as when the camera is moving and the object passes through the center of the screen.
You can use Camera.ViewportPointToRay with input of new Vector3(0.5f, 0.5f, 0) to create a ray coming from the center of the screen. From there, the process is similar to your holding code.
Also, with a little rearranging, you can re-use the raycast that is already used to check if this object was touched because it also checks if any object was touched. Only checking to start rotating when the first raycast touches nothing avoids rotating (and raycasting unnecessarily) in the situation where this object is in the center of the screen but another object was touched.
As a sidenote, it's recommended to cache the result of Camera.main because it does a FindGameObjectsWithTag internally every time you reference it and that can add up to extra computation time.
Altogether, it could look like this:
private bool rotating;
private Camera cam;
void Start()
{
holding = false;
rotating = false;
cam = Camera.main;
}
void Update()
{
// One finger
if (Input.touchCount == 1)
{
Touch touch0 = Input.GetTouch(0);
if (touch0.phase == TouchPhase.Began)
{
Ray ray;
RaycastHit hitTouch;
// test hold start
ray = cam.ScreenPointToRay(touch0.position);
if (Physics.Raycast(ray, out hitTouch, 100f))
{
if (hitTouch.transform == transform)
{
holding = true;
}
}
else // avoid rotating/raycasting again in situation
// where this object may be in center of screen
// but this or other object was touched.
{
// test rotate start
ray = cam.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
if (Physics.Raycast(ray, out hitTouch, 100f)))
{
if (hitTouch.transform == transform)
{
rotating = true;
}
}
}
}
else if (touch0.phase == TouchPhase.Moved)
{
if (holding)
{
Move();
}
else if (rotating)
{
transform.Rotate(0f, touch0.deltaPosition.x * 0.5f, 0f);
}
}
// Release
else if (touch0.phase == TouchPhase.Ended)
{
holding = false;
rotating = false;
}
}
}
I'm trying to make a platformer game that uses 2D sprites in 3D space. For some reason, though the bullet the player shoots fires correctly from the GO that's a child of the player, it won't collide with anything (enemies, other objects, etc.) in either 2D or 3D. I have a 3D character controller on the parent player GO, but that doesn't affect the bullet object I'm firing, does it?
Colliders on everything appropriately.
Tried with IsTrigger both on and off in various combinations.
Objects are on the same layer and the same Z axis position.
//BULLET FIRING SCRIPT
void Update()
{
if (Input.GetButtonDown("Fire2") && !Input.GetButton("Fire3") &&
Time.time > nextFireTime)
{
Rigidbody2D cloneRb = Instantiate(bullet,
bulletSpawn.position, Quaternion.identity) as Rigidbody2D;
cloneRb.AddForce(bulletPrefab.transform.right *
projectileForce);
nextFireTime = Time.time + fireRate;
}
}
//____________________________________________________________
//BULLET OBJECT SCRIPT
private void Start()
{
direction = Input.GetAxisRaw("Horizontal");
if (direction == -1)
{
facingLeft = true;
}
else if (direction == 1)
{
facingLeft = false;
}
else if (direction == 0)
{
facingLeft = false;
}
if (facingLeft == false)
{
rb.velocity = transform.right * speed;
Debug.Log("Fired Bullet");
}
else
{
bulletPrefab.transform.Rotate(0, 180, 0);
firePoint.transform.Rotate(0, 180, 0);
Debug.Log("Rotated Bullet");
Debug.Log("Fired Bullet Left");
rb.velocity = transform.right * speed;
}
}
// Update is called once per frame
void Update()
{
}
public void OnTriggerEnter2D(Collider2D collider)
{
Debug.Log("Bullet Hit:");
Debug.Log(collider.name);
Enemy enemy = collider.GetComponent<Enemy>();
if (enemy != null)
{
enemy.TakeDamage(damage);
}
Destroy(gameObject);
}
Expected Result: Bullet object collides with other objects, prints Debug.Log output and gets destroyed.
Actual Result: Bullet object fires through, in front of, or behind other objects that also have colliders and there is no Debug.Log output. Bullet does not get destroyed and so instantiates clones infinitely when assigned input is entered.
Fixed issue by checking Default/Default in the Layer Collision Matrix under Edit-->Project Settings-->Physics. Not sure if it's supposed to be checked by default and somehow got unchecked but checking it solved the problem.
using UnityEngine;
using System.Collections;
using System;
public class TurretScript : MonoBehaviour
{
public float rangeToPlayer;
public GameObject bullet;
// public GameObject spherePrefab;
private GameObject player; // Tag for DynamicWaypointSeek
private bool firing = false; //Firing status
private float fireTime; // Fire Time
private float coolDown = 1.00F; // Time to cool down fire
private int health = 5; //Health of turret
private bool bDead;
private Action cur;
void Start()
{
player = GameObject.FindWithTag("Player");
bDead = false;
}
void Update()
{
if (PlayerInRange())
{ // PlayerInRange is bool function defined at the end
transform.LookAt(player.transform.position); //Rotates the transform (weapon) so the forward vector points at /target (Player)/'s current position
RaycastHit hit;
if (Physics.SphereCast(transform.position, 0.5F, transform.TransformDirection(Vector3.forward), out hit))
{ //The center of the sphere at the start of the sweep,The radius of the sphere,The direction into which to sweep the sphere.
if (hit.transform.gameObject.tag == "Player")
{ //information in hit (only interested in "Player")
if (firing == false)
{
firing = true;
fireTime = Time.time; //keep the current time
GameObject bul;
Quaternion leadRot = Quaternion.LookRotation(player.transform.position); //Calculate the angular direction of "Player";
bul = Instantiate(bullet, transform.position, leadRot) as GameObject; // existing object to be copied, Position of Copy, Orientation of Copy
//Destroy(bullet, 2.0f);
}
}
}
}
if (firing && fireTime + coolDown <= Time.time) // prvious time stored in fireTime + cool down time is less --> means the firing must be turn off
firing = false;
// Destroy(GameObject.FindGameObjectWithTag("Bullet"));
if (health <= 0)
cur = Deadstate;
}
protected void Deadstate()
{
if (!bDead)
{
bDead = true;
Explode();
}
}
void Explode()
{
Destroy(gameObject, 1.5f);
}
bool PlayerInRange()
{
return (Vector3.Distance(player.transform.position, transform.position) <= rangeToPlayer); //Vector3.Distance(a,b) is the same as (a-b).magnitude.
}
void OnTriggerEnter(Collider col)
{
HealthBarScript.health -= 10f;
}
}`
This is code I wrote for a enemy turret in my current unity project. The idea is that it will fire a bullet at the player once it's in range and stop when it's out of range and when the bullet collides with the player, the player will take damage, but I'm struggling to figure out why the bullet won't do any damage. Thoughts? Very much appreciate the help!
According to my experience: bullet speed may be too fast to detect collision.
Imagine that in 2d:
frame 1:
.BB.......OOO......
frame 2:
........BBOOO......
frame 3:
..........OOO..BB..
Where OOO is your object and BB is your bullet.
To fix this you can have a bigger collider for the bullet. Other workaround like "dynamic" collider are also possible.