I'm trying to create a falling word game like z-type. Once the game starts a few words are displayed on the screen. When the user types a letter and if that matches with the first letter of any of the words displayed, an activeWord tag is added to the word. I have also created a laser script that checks if the tag is active and when that happens, it shoots the laser.
What's happening right now is that the laser is shot only once i.e when the first letter matches but doesn't shoot a laser when the remaining words are typed.
This is the laser script:
using UnityEngine;
using UnityEngine.UI;
public class Laser : MonoBehaviour {
public float speed = 10.0f;
private Vector3 laserTarget;
private void Update () {
GameObject activeWord = GameObject.FindGameObjectWithTag("activeWord");
if (activeWord && activeWord.GetComponent<Text>().text.Length > 0) {
laserTarget = activeWord.transform.position; // find position of word
transform.Translate(laserTarget * speed * Time.deltaTime); // shoot the laser
}
}
}
I'm also adding the code that I use in the display/UI field.
public void RemoveLetter() {
/* remove the first letter if its correct and so
on for the remaining letters. change the color of the word to red and add
the "activeddWord" tag.*/
text.text = text.text.Remove(0, 1);
text.color = Color.red;
text.gameObject.tag = "activeWord";
}
public void RemoveWord() { // destroy the word once all the letters match
Destroy(gameObject);
}
Can someone please have a look at the code and tell me where I'm making a mistake.
I think you have to reset the position of your laser if it reaches the target:
public float speed = 10.0f;
private Vector3 laserTarget;
private Vector3 laserOrigin;
private void Start () {
// save laser's origin position
laserOrigin = transform.position;
}
private void Update () {
GameObject activeWord = GameObject.FindGameObjectWithTag("activeWord");
if (activeWord && activeWord.GetComponent<Text>().text.Length > 0)
{
laserTarget = activeWord.transform.position; // find position of word
transform.Translate(laserTarget * speed * Time.deltaTime); // shoot the laser
float distance = Vector3.Distance(laserTarget , transform.position);
if(distance < 0.05f){ // I don't know your scaling, perhaps change the limit here!
transform.position = laserOrigin;
}
}
}
Here is one way you can do this using Instantiate() and prefabs. The benefit of this method is that it is scalable. You can create multiple lasers with minimal tweaking. Please note that to use multiple lasers you will have to remove WaitForThisLaserDestroyed;.
To get this to work you will have to start by changing your laser gameObject into a prefab and adding this script onto it:
https://docs.unity3d.com/Manual/Prefabs.html
public class Laser : MonoBehaviour
{
public float speed = 10.0f;
public Vector3 laserTarget;
public float destroyLaserAfterTime = 3f;
private void Update ()
{
transform.Translate(laserTarget * speed * Time.deltaTime);
}
}
And then on some arbitrary other object. For example an empty game object in the same scene:
public class LaserInitializer : MonoBehaviour
{
public GameObject laserPrefab;
public GameObject laserOrigin;
private GameObject WaitForThisLaserDestroyed;
private void Update ()
{
GameObject activeWord = GameObject.FindGameObjectWithTag("activeWord");
if (WaitForThisLaserDestroyed == null && activeWord && activeWord.GetComponent<Text>().text.Length > 0)
{
CreateLaser(activeWord);
}
}
private void CreateLaser(GameObject activeWord)
{
GameObject activeLaser = Instantiate(laserPrefab, laserOrigin.Transform.Position, Quaternion.identity) as GameObject;
Laser laserScript = activeLaser.GetComponent<Laser>();
laserScript.laserTarget = activeWord.transform.position;
WaitForLaserDestroyed = activeLaser;
Destroy(activeLaser, destroyLaserAfterTime);
}
}
To explain the code:
The Laser prefab has its own script for moving towards the word, and as soon as it exists and has the target passed to it, it will move towards the active word.
Somewhere else in the scene you have a game object that exists to hold the second script. Lets call it the "controller game object". It checks to see if the words are "active", as per the earlier design. When a word is active this script creates exactly one laser, and tells it to target the active word.
You have another gameobject (this can be the same one as the controller game object), that marks the origin of the laser. You can do this in other ways, but I thought that using a game object to mark the start point would be an easy way for beginners.
Related
Currently, I want to make a dash for the player in a 2D game in unity. I'm using game objects to the left and right of the player to use raycasts to check the distance from a wall in a certain area. If its too close to a wall, I need it to dash only that distance (to the wall) instead of the full distance (behind/through the wall).
This is my code for dashing in my player movement (DashCheckLeft is -0.5 from the player) (DashCheckRight is 0.5 from the player):
public float dashDistance = 3f;
float distance;
public Transform DashCheckLeft;
public Transform DashCheckRight;
private void Update() {
if(Input.GetKeyDown(KeyCode.LeftShift)) {
Debug.Log("Dashed");
Dash();
private void FixedUpdate() {
RaycastHit2D hitLeft = Physics2D.Raycast(DashCheckLeft.position, Vector2.left, -dashDistance);
RaycastHit2D hitRight = Physics2D.Raycast(DashCheckRight.position, Vector2.right, dashDistance);
if(hitLeft.collider != null) {
distance = Mathf.Abs(hitLeft.point.x - transform.position.x);
}
else if(hitRight.collider != null) {
distance = Mathf.Abs(hitRight.point.x - transform.position.x);
}
else {
distance = dashDistance;
}
}
private void Dash() {
rb.position = new Vector2(rb.position.x + distance, rb.position.y);
}
The problem now is that apperently I'm only dashing the distance between the player and the DashChecks (0.5f) instead of the intended 3f and I believe it may be because the raycasts are hitting the collider of the player somehow, but changing my player to the "Ignore Raycast" layer makes it fall into the floor, and also doesn't fix the issue.
I worked on this problem, and found the answer. Basically, your raycasts were detecting the player's collider too. There is a simple fix for this. You should just add the fourth argument to your raycast. The fourth argument is a LayerMask. A LayerMask is like a tag, but you can have several on at the same time. This is what I changed the code to (I made some extra adjustments too):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
Rigidbody2D rb;
public float dashDistance = 3f;
public LayerMask floor;
float distance;
private void Start()
{
rb = GetComponent<Rigidbody2D>();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.LeftShift))
{
RaycastHit2D left = Physics2D.Raycast(transform.position, -transform.right, dashDistance, floor);
RaycastHit2D right = Physics2D.Raycast(transform.position, transform.right, dashDistance, floor);
if (left.collider != null)
{
print("left");
distance = Mathf.Abs(left.point.x - transform.position.x);
distance = -distance;
}
else if (right.collider != null)
{
print("right");
distance = Mathf.Abs(right.point.x - transform.position.x);
}
else
{
distance = dashDistance;
}
print(distance);
Debug.Log("Dashed");
Dash();
}
void Dash()
{
rb.position = new Vector2(rb.position.x + distance, rb.position.y);
}
}
}
(when I say 'world objects', I mean the objects that make up the terrain. For example the ground or trees) You should add a new layer by clicking on the world objects. Then, you should look at the top of the inspector, then click the 'Layer Default' dropdown menu, and click the 'Add Layer...' button. Then write a new name on that list. Go back to the world game object, and click the same dropdown menu, and select the layer that you want for the world. You should make sure that all of the world objects have the same layer. Go back to the player object, and in the inspector, set the 'floor' variable to the same layer as the world. If that didn't make sense, here is a link of how to set layers.
this should be a very simple answer. I'm following a Unity with C# tutorial for making a simple Space Invaders game, and at one point it is shown that when our enemyHolder object has no child objects left (when all enemies are destroyed) the attached text under the winText function should be displayed.
So we have
if (enemyHolder.childCount == 0)
{
winText.enabled = true;
}
When I run the code the text isn't displayed after the enemies are destroyed and no child object is left. It's like the code stops getting read at that point, although the character is still movable and you can generate new shots.
If I create two "Enemy" child objects and tell it to display the winText rather when the childCount reaches 1, it does work.
So why is it not working when the function calls for == 0?
EDIT: Complete class code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class EnemyController : MonoBehaviour
{
private Transform enemyHolder;
public float speed;
public GameObject shot;
public Text winText;
public float fireRate = 0.997f;
// Start is called before the first frame update
void Start()
{
winText.enabled = false;
InvokeRepeating("MoveEnemy", 0.1f, 0.3f);
enemyHolder = GetComponent<Transform>();
}
void MoveEnemy()
{
enemyHolder.position += Vector3.right * speed;
foreach (Transform enemy in enemyHolder)
{
if (enemy.position.x < -10.5 || enemy.position.x > 10.5)
{
speed = -speed;
enemyHolder.position += Vector3.down * 0.5f;
return;
}
if (enemy.position.y <= -4)
{
GameOver.isPlayerDead = true;
Time.timeScale = 0;
}
if (enemyHolder.childCount == 1)
{
CancelInvoke();
InvokeRepeating("MoveEnemy", 0.1f, 0.25f);
}
if (enemyHolder.childCount == 0)
{
winText.enabled = true;
}
}
}
}
Your code is inside the void MoveEnemy() function.
I'm assuming your script is attached to in-game enemies. Your code doesn't run because the MoveEnemy function no longer runs if there are no enemies.
So, you need to handle enemy movement and scene handling in different scripts.
The code that checks the enemy holder's number of children should be placed inside a void Update() function. This Update() function should be placed on an object that never gets deleted. Its advantage is that it runs every frame.
As a convention, devs generally use separate empty objects or even the camera to attach scripts which contain Update functions that handle the scene. Good luck!
Read more on Update
As you can see in the screenshoot I can't see prefabs in the game tab but only in the editor. I have made a simple function for shooting(not finished yet), it works fine, it spawns the prefabs but i can't see them in the game tab, I have already tried changing the Sorting Layer, move the camera, change Z position but nothing appen.
This is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerAttack : MonoBehaviour
{
[SerializeField]
float delayBetweenShots = 0.4f;
float timePassedSinceLast = 0f;
// Start is called before the first frame update
void Start()
{
timePassedSinceLast = delayBetweenShots;
}
// Update is called once per frame
void Update()
{
Aiming();
Shooting();
}
void Aiming()
{
var objectPos = Camera.main.WorldToScreenPoint(transform.position);
var dir = Input.mousePosition - objectPos;
transform.rotation = Quaternion.Euler(new Vector3(0,0,Mathf.Atan2(-dir.x, dir.y) * Mathf.Rad2Deg));
}
void Shooting()
{
if(Input.GetMouseButton(0) && timePassedSinceLast >= delayBetweenShots)
{
GameObject bullet = (GameObject)Instantiate(Resources.Load("bullet"), transform.position, transform.rotation);
timePassedSinceLast = 0f;
}
else
{
timePassedSinceLast += Time.deltaTime;
}
}
}
The prefabs get instantiated correctly. As others suggested as well, the best way to find "lost" objects in your game is to shoot some stuff, pause the game, go into scene view, turn on 3D mode and double click one of the prefabs in the hierarchy. The camera will take you straight to your object.
I have a spawner object. Every time a gameobject is spawned, I want that object to move randomly (wandering). The problem in my script is that the gameobject movement is very random (jittery). How can I solve this?
void Start ()
{
InvokeRepeating("SpawnNPC", Spawntime, Spawntime);
}
// Update is called once per frame
void Update () {
population = GameObject.FindGameObjectsWithTag("NPCobject");
for (int i = 0; i < population.Length; i++)
{
getNewPosition();
if (population[i].transform.position != pos)
{
population[i].transform.position = Vector3.MoveTowards(population[i].transform.position, pos, .1f);
}
}
}
void getNewPosition()
{
float x = Random.Range(-22, 22);
float z= Random.Range(-22, 22);
pos = new Vector3(x, 0, z);
}
I made the New randomize vector in different method, because I plan to change it with pathfinder function and make it in different thread/task.
You are choosing a new direction every single frame. That will always be very jittery. You wan't to only change direction after, at least, a small interval. Here is a link to one way to do that from Unity's website. https://docs.unity3d.com/ScriptReference/MonoBehaviour.InvokeRepeating.html
What about using Navigation? As you said wandering, I thought it would give you a nice result and also make your code simple.
The following screenshot is a sample with Navigation. The moving game objects are also changing their direction nicely, although it cannot be seen in the sample because the game object is capsule...
Ground game object in the sample program has NavMesh. See here to build NavMesh.
Agent game object has NavMeshAgent Component. See here to set it up.
Th Behaviour class below is for Agent game object.
using UnityEngine;
using UnityEngine.AI;
public class NavAgentBehaviour : MonoBehaviour {
public Transform[] Destinations { get; set; }
// Use this for initialization
void Start ()
{
InvokeRepeating("changeDestination", 0f, 3f);
}
void changeDestination()
{
var agent = GetComponent<NavMeshAgent>();
agent.destination = Destinations[Random.Range(0, Destinations.Length)].position;
}
}
The next Behaviour class is just for spawning the Agent and setting up the destinations. On Unity, set it to whatever game object in the scene, and allocate game objects to the fields.
using UnityEngine;
public class GameBehaviour : MonoBehaviour {
public GameObject Agent;
public Transform SpawnPoint;
public Transform Destination1;
public Transform Destination2;
public Transform Destination3;
// Use this for initialization
void Start()
{
Agent.SetActive(false);
InvokeRepeating("Spawn", 0f, 2f);
}
void Spawn() {
var newAgent = Instantiate(Agent);
newAgent.GetComponent<NavAgentBehaviour>().Destinations = new[] { Destination1, Destination2, Destination3 };
newAgent.transform.position = SpawnPoint.position;
newAgent.SetActive(true);
}
}
Increase the number of destination, to make the moves look more random. By the way, the destinations do not need to be specified by game objects, which is only for making it easy to see the sample program's behaviour.
The source of the jitteriness comes from the fact that you are updating the position to move every frame so your objects never have a consistent location to move to. I would instead suggest attaching a new script to each of your objects that individually handles their movement. In that script you could do something like the following, which has a delay to keep the target position for more than 1 frame.
float delaytimer;
Vector3 pos;
void Start () {
getNewPosition(); // get initial targetpos
}
void Update () {
delaytimer += Time.deltaTime;
if (delaytimer > 1) // time to wait
{
getNewPosition(); //get new position every 1 second
delaytimer = 0f; // reset timer
}
transform.position = Vector3.MoveTowards(transform.position, pos, .1f);
}
void getNewPosition()
{
float x = Random.Range(-22, 22);
float z= Random.Range(-22, 22);
pos = new Vector3(x, 0, z);
}
You are changing the direction they are moving in every frame, thats what is causing the jitter.
You could wait a few moments before you change the direction, perhaps something like this.
// Outside update
float betweenChanges = 2;
float lastChange = 0;
// Inside update
if(Time.realtimeSinceStartup > lastChange)
{
// Change directions
// ...
lastChange = Time.realTimeSinceStart + betweenChanges;
}
You could also solve this by using InvokeRepeating or a Coroutine.
If you dont want all the NPC's to change direction at the same time and still plan on controlling every NPC from the same class like in your example, you should perhaps add a timer for each NPC instance instead and use that to decide when to change its direction.
A better idea would be to let each NPC have its own Movement-script.
So I have an assignment that I have to do for school where an enemy shoots at the player in a gun type game. I have read about the LookAt() function in the Unity tutorials and used it to my knowledge of it. However it doesn't seem to be working. The following code is what I have so far:
public class EnemyControl : MonoBehaviour {
private Rigidbody rb;
public GameObject Bullet_Emitter2;
public GameObject EnemyBullet;
public float speedOfBullet;
private int score;
public Text countText;
public Text winText;
private GameObject Temporart_Bullet_Handler2;
public Transform player;
public Transform enemy;
void Start()
{
rb = GetComponent<Rigidbody>();
score = 0;
}
private void Update()
{
enemy.LookAt(player);
}
// Update is called once per frame
void FixedUpdate () {
int fire = Random.Range(0, 100);
if(fire == 0 || fire == 1 || fire == 5)
{
Temporart_Bullet_Handler2 = Instantiate(EnemyBullet, Bullet_Emitter2.transform.position, Bullet_Emitter2.transform.rotation) as GameObject;
Temporart_Bullet_Handler2.transform.Rotate(Vector3.right * 90);
Rigidbody Temporary_Rigid_Body;
Temporary_Rigid_Body = Temporart_Bullet_Handler2.GetComponent<Rigidbody>();
Temporary_Rigid_Body.AddForce(transform.up * speedOfBullet);
Destroy(Temporart_Bullet_Handler2, 20.0f);
}
}
}
The problem is my enemy now just looks at the ground instead of me and then just shoots downwards. Is there a way I can fix this? I have attached an image of what it looks like to a player playing the game?
Before putting in the LookAt() function my spaceman would just be stationary and fire bullets in a straight line but I need the AI to track the player instead of stationary. Is there another way to do this without using LookAt() or am I using this function wrong?
Thanks for the help in advance?
I would post this in comments but I can't yet because of low reputation
As Serlite pointed out, LookAt() only faces the GameObject's trasnform.forward (which is desired to be (0, 0, 1) when the object is not rotated) towards the target passed as argument.
To 'fix' this simply add an empty GameObject to your Hirarchy and set your AI object as its child
Empty GameObject
- AI GameObject (with your script)
Now you can set a global rotation for your object (Rotate the parent) in a way that the AI's forwards faces z+.