These past month+ I learned many things by making a game in Unity.I have a lot of fun doing so. But some thing are still confusing me. I'm trying to setup a skill to the character and it goes almost well. When the character is casting the skill, the skill goes behind the character and not in front. So i thought to play with positions and rotations to make it work but still nothing. Worth to mention that the prefab has it's own motion. So my code so far is this. So help would be great and some teaching about the logic behind the skills system would be much appreciated. So take a look:
using UnityEngine;
public class MagicSkill : MonoBehaviour
{
public GameObject hand; // Players right hand
public GameObject fireballPrefab; // Fireball particle
Vector3 fireballPos; // Adding the transform.position from the players hand
Quaternion fireballRot; // Adding the rotation of the players hand
private bool isPressed = false; //Skill button (mobile)
public Animator animator; // Casting spell animation
void Update()
{
fireballPos = hand.transform.position; // Getting the position of the hand for Instatiating
fireballRot.x = hand.transform.rotation.x; // Getting the rotation of the hand for x Axis
fireballRot.y = hand.transform.rotation.y; // Getting the rotation of the hand for y Axis (Here i try to modify the values but still nothing)
fireballRot.z = hand.transform.rotation.z; // Getting the rotation of the hand for z Axis
if (isPressed == true)
{
animator.SetBool("Magic", true);
if (hand.transform.position.y >= 20) // Here I got the exact position of the hand for when to
Instatiate the skill
{
for (int i = 0; i < 1; i++) // For some reason it instatiates too many prefabs (I think it's because it's in Update method)
{
Instantiate(fireballPrefab, fireballPos, Quaternion.Euler(fireballRot.x, fireballRot.y, fireballRot.z));
Invoke("Update", 2.0f); // I'm trying to slow down the pressed button so that it want spawn every frame
}
}
}
else
{
animator.SetBool("Magic", false);
}
}
public void MagicSkills()
{
if (isPressed == false)
{
isPressed = true;
}
else
{
isPressed = false;
}
}
}
After some playing around with the code I managed to find a solution.Here I post the working code for me at least.Maybe it will help someone else too.For this to work properly you must remove the Event Trigger OnPointerUp from your button.Many thanks for the help Guilherme Schaidhauer Castro
using UnityEngine;
public class MagicSkill : MonoBehaviour
{
public GameObject hand; // Players right hand
public GameObject fireballPrefab; // Fireball particle
public Animator animator; // Casting spell animation
Vector3 fireballPos; // Adding the transform.position from the players hand
private bool isPressed = false; //Skill button (mobile)
void Update()
{
fireballPos = hand.transform.position; // Getting the position of the hand for Instatiating
if (isPressed == true)
{
animator.SetBool("Magic", true);
if (hand.transform.position.y >= 20) // Here I got the exact position of the hand for when to Instatiate the skill
{
Instantiate(fireballPrefab, fireballPos, Quaternion.identity);
isPressed = false;
}
}
else
{
animator.SetBool("Magic", false);
}
}
public void MagicSkills()
{
if (isPressed == false)
{
isPressed = true;
}
else
{
isPressed = false;
}
}
}
Keep a reference to the character's transform and use that transform to instantiate the fireball instead of using the rotation from the hand. The rotation of the hand is not changing in relation to the body, so that's why the ball is always going in the same direction.
Related
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
I'm trying to change the direction of the enemy when collides with a wall or the character. However, it doesn't work for the wall that the enemy bumps into, but only stays where it is.
Here's what I mean:
In this picture, the goomba switches the direction when bumps into Mario, however the same does not happen when the wall is hit.
What happens instead is this:
The Goomba stands still.
Here's the code of the goomba:
public class GoombaController : MonoBehaviour
{
public float speed = 1f;
private Vector2 force = Vector2.left;
void Update()
{
transform.Translate(force * Time.deltaTime * speed);
}
void OnCollisionEnter2D(Collision2D coll) {
if ((this.transform.position.x - coll.collider.transform.position.x) < 0) {
force = Vector2.left;
} else if ((this.transform.position.x - coll.collider.transform.position.x) > 0) {
force = Vector2.right;
}
}
}
Also, I use a tile map to generate the level, and the ground and the walls are on the same tile map.
If anything else is needed as info, just leave a comment below the question.
How can I solve this?
I want to add doors to my Unity project and so far I have gotten the code to work with my door but I can open it from any distance in the scene. I want to be able to only open it from a small distance away but I cannot figure out how.
public class Door : MonoBehaviour
{
public bool doorIsOpen;
public Transform closedPos;
public Transform openPos;
public float openSpeed;
public float openRadius;
void Update()
{
if(Physics.CheckSphere(gameObject.transform.position, openRadius))
{
if(Input.GetKeyDown(KeyCode.E))
{
doorIsOpen = !doorIsOpen;
}
}
if(doorIsOpen == true)
{
OpenDoor();
}
if(doorIsOpen == false)
{
CloseDoor();
}
}
void OpenDoor()
{
doorIsOpen = true;
gameObject.transform.position = openPos.position;
gameObject.GetComponent<Animator>().SetTrigger("OpenDoor");
gameObject.GetComponent<Animator>().SetBool("DoorIsClosed", false);
gameObject.GetComponent<Animator>().SetBool("DoorIsOpen", true);
}
void CloseDoor()
{
doorIsOpen = false;
gameObject.transform.position = closedPos.position;
gameObject.GetComponent<Animator>().SetTrigger("CloseDoor");
gameObject.GetComponent<Animator>().SetBool("DoorIsOpen", false);
gameObject.GetComponent<Animator>().SetBool("DoorIsClosed", true);
}
}
Prerequisites
Add a sphere with a collision body around the door instance in Unity. This sphere functions as the radius which will trigger the ChangeDoorState method.
Change the update method
The update will look at any collisions happening in the specified sphere. If there is at least one object in range (the collision sphere) of the door, then it opens or closes the door instance. Source: https://docs.unity3d.com/ScriptReference/Physics.OverlapSphere.html
void Update()
{
Collider[] hitColliders = Physics.OverlapSphere(center, radius);
if (hitColliders.Length > 0)
{
ChangeDoorState();
}
}
Merged the OpenDoor and CloseDoor methods
void ChangeDoorState()
{
doorClosureState = doorClosure ? true : false;
gameObject.transform.position = doorClosureState ? closedPos.position : openPos.postition;
gameObject.GetComponent<Animator>().SetTrigger("DoorClosure");
gameObject.GetComponent<Animator>().SetBool("DoorIsOpen", doorClosureState);
gameObject.GetComponent<Animator>().SetBool("DoorIsClosed", !doorClosureState);
}
You can increase the value of 'openRadius', the game is creating a sphere at gameObject.transform.position with a radius of 'openRadius' and checking if there is any colliders overlapping the sphere.
//...
if(Physics.CheckSphere(gameObject.transform.position, openRadius))
//...
One issue is that you permanently set the triggers in every frame. You would only want to do so when you hit the key.
Then also Physics.CheckSphere checks whether there is any collider within the range. This could be any collider, not only the player. To make sure it only works if the player is the one in range you should definitely use Layers and give the player its own layer e.g. "Player". Then you can pass the layerMask parameter to make sure the doors only check for the player's layer.
so I would simply use
// if possible already reference this in the Inspector
[SerializeField] private Animator _animator;
[Tooltip("Here select only the Layer(s) which you assigned to your Player object(s)")]
[SerializeField] LayerMask _playerLayer;
private void Awake()
{
// as fallback get it ONCE on runtime
if(!_animator) _animator = GetComponent<Animator>();
}
private void Update()
{
// Afaik the Key is way cheaper to check so do it first
// and use physics only if actually necessary
if(Input.GetKeyDown(KeyCode.E))
{
// Only check for the Player's Layer and ignore other colliders
if(Physics.CheckSphere(transform.position, openRadius, _playerLayer.value))
{
ToggleDoorState();
}
}
}
// Since manually changing the flag in the Inspector will not have
// any effect anymore in order to be able to test it you can use
// the context menu of this component in the Inspector
[ContextMenu(nameof(ToggleDoorState))]
private void ToggleDoorState()
{
// invert the flag
doorIsOpen = !doorIsOpen;
// use the flag in ternary expressions
transform.position = doorIsOpen ? openPos.position : closedPos.position;
_animator.SetTrigger(doorIsOpen ? "OpenDoor" : "CloseDoor");
// use the value of the flag diectly
_animator.SetBool("DoorIsClosed", !doorIsOpen);
_animator.SetBool("DoorIsOpen", doorIsOpen);
}
It is seems a bit though as if you have a bit of redundancy in your animator. I would either use the Bools in order to trigger a transition or use the Triggers, to have both seems odd.
I am developing a 2D game in unity. One of the features in the game is to shoot projectiles using the left click of the mouse. Upon releasing the left click the projectile gets fired with certain amount of force that depends on how long the player held the left click.
The problem is that sometimes when I release the left click the game doesnt seem to detect it and the release portion of the code doesnt get executed until I click and release again. I may not sound like a big problem but input reliability will play a fundamental role in this game.
So, is there any way to make mouse input more realiable? I have already tried using Input.GetMouseButtonDown and different kinds of conditionals in order to make it more reliable, but it hasnt worked out. Thank you in advance!
Here is the code I am using:
using UnityEngine;
using System.Collections;
public class ShootPlasma : MonoBehaviour {
//prefab
public Transform bulletPrefab;
//Reloading variables:
public int numberOfBullets;
private int numberOfBulletsRecord;
private bool canShoot=true;
public float timeToReload;
private float timeToReloadRecord;
//direction and bullet Speed variables:
Transform sightPosition;
public Vector3 SpawnRiseVector;
private Vector2 direction;
public float bulletBoost;
private float bulletBoostRecord;
public float MAX_BOOST;
//Arrow Guide
public Transform aimingArrow;
//Helper variables;
private CharacterController2D charControllerScript;
void Start () {
timeToReloadRecord = timeToReload;
numberOfBulletsRecord = numberOfBullets;
charControllerScript = transform.parent.GetComponent<CharacterController2D> ();
bulletBoostRecord = bulletBoost;
sightPosition = GetComponent<Transform> ();
aimingArrow.GetComponentInChildren<Renderer>().enabled=false;
}
// Update is called once per frame
void FixedUpdate () {
if(numberOfBullets<=0){
canShoot=false;
if(!canShoot){
timeToReload-=Time.deltaTime;
//if the waiting time has ended restart variables.
if(timeToReload<=0.0f){
canShoot=true;
timeToReload=timeToReloadRecord;
numberOfBullets=numberOfBulletsRecord;
}
}
}
////////////////////////////////// SHOOTING CODE ////////////////////////////////////////////
///
/// MOUSE DOWN
/////////////////////////////////////////////////////////////////////////////////////////
else if(Input.GetMouseButton(0)&& canShoot && !Input.GetMouseButtonUp(0)){
//show the Arrow Guide:
if(aimingArrow.GetComponentInChildren<Renderer>().enabled!=true){
aimingArrow.GetComponentInChildren<Renderer>().enabled=true;
}
//calculate the distance between the mouse and the sight;
Vector3 mousePositionRelative=Camera.main.ScreenToWorldPoint(Input.mousePosition);
direction= new Vector2(mousePositionRelative.x- sightPosition.position.x,
mousePositionRelative.y- sightPosition.position.y).normalized;
//If Y is less or equal 0:
if(direction.y<=0.0f && direction.x>=0.0f){
direction=new Vector2(1.0f,0.0f);
}
else if(direction.y<=0.0f && direction.x<0.0f){
direction=new Vector2(-1.0f,0.0f);
}
//Rotate the aiming arrow
if(charControllerScript.facingFront){
if(direction.x>=0.0f){
aimingArrow.rotation=Quaternion.Euler(new Vector3(0.0f,0.0f,(Mathf.Asin(direction.y/direction.magnitude))*Mathf.Rad2Deg));
}
else if(direction.x<0.0f){
aimingArrow.rotation=Quaternion.Euler(new Vector3(0.0f,180.0f,(Mathf.Asin(direction.y/direction.magnitude))*Mathf.Rad2Deg));
}
}
else if(!charControllerScript.facingFront){
if(direction.x>=0.0f){
aimingArrow.rotation=Quaternion.Euler(new Vector3(0.0f,180.0f,(Mathf.Asin(direction.y/direction.magnitude))*Mathf.Rad2Deg));
}
else if(direction.x<0.0f){
aimingArrow.rotation=Quaternion.Euler(new Vector3(0.0f,0.0f,(Mathf.Asin(direction.y/direction.magnitude))*Mathf.Rad2Deg));
}
}
Debug.Log(direction);
//Charge
bulletBoost+=Time.deltaTime*bulletBoost;
if(bulletBoost>=MAX_BOOST){
bulletBoost=MAX_BOOST;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/// MOUSE UP
/// ///////////////////////////////////////////////////////////////////////////////////////////////
else if(Input.GetMouseButtonUp(0)&& canShoot && !Input.GetMouseButton(0)){
//Hide the Arrow Guide:
if(aimingArrow.GetComponentInChildren<Renderer>().enabled!=false){
aimingArrow.GetComponentInChildren<Renderer>().enabled=false;
}
//Fire
var shootPrefab= Instantiate(bulletPrefab,sightPosition.position+SpawnRiseVector,Quaternion.identity) as Transform;
shootPrefab.GetComponent<Rigidbody2D>().AddForce(direction*bulletBoost);
bulletBoost=bulletBoostRecord;
//Reduce the Ammo by one:
numberOfBullets-=1;
}
}
}
The problem is that you are using FixedUpdate() instead of Update(). FixedUpdate() is called in constant intervals(not necessary each frame). So, the Input.GetMouseButtonUp() may be missed some time between 2 calls of FixedUpdate(). You should better use Update() when you are handling input.
I am making a pokemon clone.
It is a top down tile based rpg.
So I added 4 gameobjects on the player itself. Called up, down, left, right.
I placed them around the player. I use this set up to check if the gameObject up. Is standing on water. If that is true then the player cannot go up. It is my work around for collision. However my children don't want to follow my parent. All I did was I dragged the 4 for gameobjects onto the player, and added this c# script:
using UnityEngine;
using System.Collections;
public class PlayerController : MonoBehaviour
{
public LayerMask GroundLayer;
Vector2 startPoint;
Vector2 endPoint;
private float increment;
bool isMoving;
public float speed;
public tk2dTileMap tilemap;
public GameObject up;
public GameObject down;
public GameObject left;
public GameObject right;
void Start()
{
startPoint = transform.position;
endPoint = transform.position;
}
void Update()
{
if (increment <= 1 && isMoving == true)
{
increment += speed / 100;
}
else
{
isMoving = false;
}
if (isMoving)
{
transform.position = Vector2.Lerp(startPoint, endPoint, increment);
}
if (Input.GetKey(KeyCode.UpArrow) && isMoving == false)
{
if (MovementEvents(up) == 0)
{
move(up);
}
}
else if (Input.GetKey(KeyCode.DownArrow) && isMoving == false)
{
if (MovementEvents(down) == 0)
{
move(down);
}
}
else if (Input.GetKey(KeyCode.LeftArrow) && isMoving == false)
{
if (MovementEvents(left) == 0)
{
move(left);
}
}
else if (Input.GetKey(KeyCode.RightArrow) && isMoving == false)
{
if (MovementEvents(right) == 0)
{
move(right);
}
}
}
int MovementEvents(GameObject direction)
{
switch (tilemap.GetTileIdAtPosition(direction.transform.position, 0)) //gettileIdAtPosition returns the id of a tile in this case 0 is grass and 1 is water
{
case 0:
return 0;
default:
return 1;
}
}
void move(GameObject direction)
{
increment = 0;
isMoving = true;
startPoint = transform.position;
endPoint = direction.transform.position;
}
}
I am using 2d toolkit.
I know it is probaly because i am setting them in the wrong way but what am i doing wrong?
You are creating new GameObjects in your script (up, down, etc.) that have no relation to the objects you created in the editor. There are two solutions to this:
1) You need to assign up, down, etc. to the GameObjects you created in the editor. You can do this by editing your Start method to include:
up = GameObject.Find( "up" );
down = GameObject.Find( "down" );
// etc.
Replacing "up" with whatever you named the up GameObject in the editor.
2) Assign up, down, etc. as children to your parent GameObject using
up.transform.parent = parentGameObject.transform;
down.transform.parent = parentGameObject.transform;
// etc.
Note that if you go this route, then the up, down, etc. objects you made in the editor will not be used. You also will need to translate the new objects (after setting their parent) or else they will just sit at (0,0,0) relative to the parent.
Edit in response to the comments
If I am understanding the problem correctly, then it seems you are just making a mistake in how you add the up, down, etc. objects as children in the editor. Simply drag them all onto the parent at the same level:
Then whenever the TestParent object moves, the other objects stay at the same relative position to the parent, and thus move as well. Then if you add the GameObjects to a script like so:
You can move the objects individually (while still being relative to the parent). With the following example script, the parent object moves up (Y+) at one unit a second. The Down object also moves up at an additional one unit per second. This results in the ParentTest, Up, Left, Right maintaining formation, moving one unit per second up, while Down moves at two units per second and slowly moves past them.
using UnityEngine;
using System.Collections;
public class DeleteMe : MonoBehaviour
{
public GameObject Up;
public GameObject Down;
public GameObject Left;
public GameObject Right;
void Start( )
{
}
// Update is called once per frame
void Update( )
{
Move( gameObject );
Move( Down );
}
private void Move( GameObject obj )
{
Vector3 position = obj.transform.position;
position.y += UnityEngine.Time.deltaTime;
obj.transform.position = position;
}
}
A screenshot of them at the start:
Another after some time has elapsed:
Finally note that you do not need to have public GameObjects in the script and then drag-and-drop them onto the script to access children. The same result can be achieved by modifying the script to:
private GameObject Up;
private GameObject Down;
private GameObject Left;
private GameObject Right;
void Start( )
{
Up = transform.Find( "Up" ).gameObject;
Down = transform.Find( "Down" ).gameObject;
Left = transform.Find( "Left" ).gameObject;
Right = transform.Find( "Right" ).gameObject;
}
This prevents the tedious nature of having to drag-and-drop onto the script itself, and also allows you to access children that may be added to the parent dynamically, and thus unable to drag-and-drop.
Hope this has helped!