I'm doing an unity game and I'd like to show text in a panel letter by letter (like if someone was typing).
I've already looked a few things (coroutines,IEnumerator) but didn't figure out how to do it.
My GO is a prefab that I instantiate.I don't instantiate in Scene window, all the instantiations are done in the code. And it't not a MonoBehaviour class.
This is the prefab I instantiate :
InteractionIntro = Instantiate(Resources.Load("Prefab/Play/Interaction") as GameObject);
InteractionIntro.transform.SetParent(canvasGameObject.transform, false);
InteractionIntro.transform.localPosition = new Vector3(0, 200, 0);
public void SetInteractionText(string text)
{
InteractionIntro.GetComponentInChildren<Text>().text = text;
}
The hierarchy looks like this :
So I'd like that the "Text" component has a letter-by-letter effect.
Any idea on how to solve this ?
#UPDATE : maybe adding a script to my prefab ?
#UPDATE : i read the text that's displayed from a file.
#EDIT : so i've created a script. Here's my code:
public class TypingEffect : MonoBehaviour
{
public GameObject GO;
public float delay = 0.1f;
public string fullText;
private string currentText = "";
// Use this for initialization
void Start () {
StartCoroutine(ShowText());
}
IEnumerator ShowText(){
for(int i = 0; i <= fullText.Length; i++){
currentText = fullText.Substring(0,i);
GO.GetComponent<Text>().text = currentText;
yield return new WaitForSeconds(delay);
}
}
}
It works. Text is displayed letter by letter. But its a text I wrote on inspector:
How do I link text to the setInteractionText method ?
#UPDATE : managed to do it, but it only writes letter-by-letter first text, if I display second text it does not work anymore. maybe reset ?
I solved it using a separate script.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TypingEffect : MonoBehaviour
{
public GameObject GO;
public float delay = 0.05f;
private string fullText;
private string currentText = "";
// Use this for initialization
void Start () {
StartCoroutine(ShowText());
}
void Update()
{
}
IEnumerator ShowText()
{
currentText="";
for(int i = 0; i <= fullText.Length; i++){
currentText = fullText.Substring(0,i);
GO.GetComponent<Text>().text = currentText;
yield return new WaitForSeconds(delay);
}
}
public void setText(string text)
{
this.fullText = text;
StartCoroutine(ShowText());
}
}
The method setText is called in another class every time I need to set another text. It's important to not forget to call StartCoroutine again.
Related
Hello everyone I have a question. I have 2 script files on Unity.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Rendering.Universal;
public class CycleFinal : MonoBehaviour
{
//Lights
public Light2D GlobLight;
public Light2D SptLight;
//Displays
public Text timeDisplay;
public Text moveDisplay;
public Text phaseDisplay;
//Others
[SerializeField]
public float cdTimer = 20f;
public float moveCount = 20f;
void Start()
{
timeDisplay.GetComponent<Text>();
moveDisplay.GetComponent<Text>();
phaseDisplay.GetComponent<Text>();
moveCount = 0f;
DayTime();
}
void Update()
{
cdTimer -= Time.deltaTime;
if(cdTimer <0){
NightTime();
}else if(moveCount <0){
DayTime();
}
timeDisplay.text = "Day Time left: " + Mathf.Round(cdTimer);
moveDisplay.text = "Movement left: " + Mathf.Round(moveCount);
}
public void DayTime(){
phaseDisplay.text = "Day";
SptLight.intensity = 0;
GlobLight.intensity = 1;
moveCount = 0;
}
public void NightTime(){
phaseDisplay.text = "Night";
SptLight.intensity = 1;
GlobLight.intensity = 0;
moveCount = 20;
cdTimer = 0;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
public class PlayerController : MonoBehaviour
{
public CycleFinal script;
private PlayerMovement controls;
[SerializeField]
private Tilemap groundTilemap;
[SerializeField]
private Tilemap collisionTilemap;
private void Awake() {
controls = new PlayerMovement();
}
private void OnEnable() {
controls.Enable();
}
private void OnDisable() {
controls.Disable();
}
void Start()
{
controls.Main.Movement.performed += ctx => Move(ctx.ReadValue<Vector2>());
}
private void Move(Vector2 direction){
if(CanMove(direction))
transform.position += (Vector3)direction;
}
private bool CanMove(Vector2 direction){
Vector3Int gridPosition = groundTilemap.WorldToCell(transform.position + (Vector3)direction);
if(!groundTilemap.HasTile(gridPosition) || collisionTilemap.HasTile(gridPosition) || script.moveCount <= 0)
return false;
else
return true;
}
}
I am trying to decrease moveCount variable on every move my character made. (I'm planing to do this thing on PlayerController script Move function.) I accomplished this while moveCount variable is a variable outside of the NightTime function but when I move that moveCount variable in to the NightTime function I couldn't change its value. How can I do that?
Also main thing I want to do with those 2 script is the player will have x seconds of day time and x moves on nighttime when x seconds ends the game turns into night time and when x moves ends the game turns into day time.
I am doing a 2d game and I want my player to be able to change bullets when he collides with an object(power) and destroy that object. I have a script and I was thinking that I need to implement 2 variables prefab ON/Off but now thinking much more I want to change with the help of a tag ( My player has in his script a public Rigidbody2D bullet) and this function
void Fire()
{
if (photonView.IsMine)
{
var firedBullet = Instantiate(bullet, barrel.position, barrel.rotation);
firedBullet.AddForce(barrel.up * bulletSpeed);
}
}
this is the script that I was working on for switching bullets but I think it will not work to change a bullet that I add in the inspector for the Character script , to disable from this script and add other bullet . How I can make it by tag?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WeaponSwitching : MonoBehaviour
{
[SerializeField] private GameObject pickupEffect;
public GameObject[] DisablePrefab;
public GameObject[] EnablePrefab;
public int selectBullet = 0;
// Start is called before the first frame update
// Update is called once per frame
public void Bullet(Character bullet)
{
var effect = Instantiate(pickupEffect, transform.position, transform.rotation);
foreach (GameObject disable in DisablePrefab)
{
disable.SetActive(false);
}
foreach (GameObject enable in EnablePrefab)
{
enable.SetActive(true);
}
Destroy(gameObject);
Destroy(effect, 3.0f);
}
}
and I try this think with a BulletSwitch script to call the function from Weapon Switching script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BulletSwitch : MonoBehaviour
{
[SerializeField] private Character bullet;
// Start is called before the first frame update
private void Awake()
{
if (!bullet) bullet = GetComponent<Character>();
}
private void OnTriggerEnter2D(Collider2D other)
{
// or whatever tag your powerups have
if (!other.CompareTag("Bullet"))
{
Debug.LogWarning($"Registered a collision but with wrong tag: {other.tag}", this);
return;
}
var Bullet = other.GetComponent<WeaponSwitching>();
if (!Bullet)
{
Debug.LogError($"Object {other.name} is tagged PowerUp but has no PowerUp component attached", this);
return;
}
Debug.Log("Found powerup, pick it up!", this);
Bullet.Bullet(bullet);
}
}
inspector character
after my player collides with an object the bullets disappear.
You could make an array of prefabs and change to the correct bullet type when you hit a powerup by setting a CurrentBulletType variable to one of the types from the array. So when you hit powerup X, change bullet type to one of the types from the array.
public GameObject currentPrefab;
public GameObject[] bulletPrefabs;
private void OnTriggerEnter2D(Collider2D other)
{
string tagString = other.tag;
bool foundObject = true;
switch (tagString)
{
case "standard bullet":
currentPrefab = bulletPrefabs[0];
break;
case "fancy bullet":
currentPrefab = bulletPrefabs[1];
break;
case "even fancier bullet":
currentPrefab = bulletPrefabs[2];
break;
default:
foundObject = false;
break;
}
if (foundObject) other.gameObject.SetActive(false);
}
You can instead use a dictionary to directly reference the correct prefab by string name (tag) to make it easy to add more bullet types.
//dictionairy to reference bullet types quickly
public Dictionary<string, BulletTypes> bulletLibrary = new Dictionary<string, BulletTypes>();
//set these in the inspector
public BulletTypes[] bulletTypes;
[System.Serializable]
public struct BulletTypes
{
public string bulletName;
public GameObject prefab;
//public int bulletPower; //more data if you wish
}
private void Start()
{
//fill the dictionary based on the filled bullet type array in inspector
for (int i = 0; i < bulletTypes.Length; i++)
{
if (bulletTypes[i].bulletName != "")
bulletLibrary.Add(bulletTypes[i].bulletName, bulletTypes[i]);
else
print("bullet added, but name empty");
}
}
private void OnTriggerEnter2D(Collider2D other)
{
string tagString = other.tag;
//if the collided tag is in the dictionary, we can reference the bullet type from that dictionairy
if (bulletLibrary.ContainsKey(tagString))
{
print("spawn: " + bulletLibrary[tagString].bulletName);
print("prefab: " + bulletLibrary[tagString].prefab);
//all the data you need is in: bulletLibrary[tagString];
//Bullet.Bullet(bulletLibrary[tagString]);
other.gameObject.SetActive(false);
}
}
Sidenote: If you spawn a lot of bullets, you can use pooling. SimplePool is nice and easy for this. Instead of calling: instantiate and destroy, you call: SimplePool.Spawn() and SimplePool.DeSpawn().
If you have any more questions on this feel free to ask ;)
So I am using two push buttons (connected to an Arduino Uno) as an input to my game. The player has to push down both buttons at the same time for the character to move in the game. I want the player to hold down the buttons for a different amount of time in each level. I have a working Arduino and a working Unity timer and player script, but am not able to get the code to do what I want. What I basically want is that only when the player presses the buttons down, does the timer start counting down. Right now, the timer starts as soon as the scene begins. I know that I somehow have to reference the timer script to the button object, I have tried this but it still doesn't work. Note that the timer UI does have a Timer tag on it. I have also referenced the Player Controller script in the Timer script. Right now, Its giving me a range of errors. I have attached an image depicting these errors.error image
The Timer script:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class Timer : MonoBehaviour
{
//int startTime = 0;
public bool buttonPressed = false;
public int timeLeft;
public Text countdownText;
GameObject Character;
void Awake()
{
Character = GameObject.FindWithTag("Player");
}
public void Start()
{
//StartCoroutine("LoseTime");
BeginTimer();
}
void Update()
{
countdownText.text = ("Time Left = " + timeLeft);
if (timeLeft <= 0)
{
//StopCoroutine("LoseTime");
//countdownText.text = "Times Up!";
Invoke("ChangeLevel", 0.1f);
}
}
public void BeginTimer()
{
Character.GetComponent<PlayerController>().Update();
//gameObject.GetComponent<MyScript2>().MyFunction();
if (buttonPressed == true )
{
StartCoroutine("LoseTime");
}
else if (buttonPressed == false)
{
StopCoroutine("LoseTime");
}
}
IEnumerator LoseTime()
{
while (true)
{
yield return new WaitForSeconds(1);
timeLeft--;
}
}
void ChangeLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
The Player Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO.Ports;
public class PlayerController : MonoBehaviour
{
SerialPort sp = new SerialPort("\\\\.\\COM4", 9600);
//player == GameObject.FindWithTag("Player").GetComponent<>();
public float Speed;
public Vector2 height;
public float xMin, xMax, yMin, yMax;
public bool buttonPressed = false;
GameObject Character;
public void Awake()
{
Character = GameObject.FindWithTag("Player");
}
public void Start()
{
if (!sp.IsOpen)
{ // If the erial port is not open
sp.Open(); // Open
}
sp.ReadTimeout = 1; // Timeout for reading
}
public void Update()
{
if (sp.IsOpen)
{ // Check to see if the serial port is open
try
{
string value = sp.ReadLine();//To("Button"); //Read the information
int button = int.Parse(value);
//float amount = float.Parse(value);
//transform.Translate(Speed * Time.deltaTime, 0f, 0f); //walk
if (button == 0) //*Input.GetKeyDown(KeyCode.Space*/) //jump
{
buttonPressed = true;
Character.GetComponent<Rigidbody2D>().AddForce(height, ForceMode2D.Impulse);
Character.GetComponent<Rigidbody2D>().position = new Vector3
(
Mathf.Clamp(GetComponent<Rigidbody2D>().position.x, xMin, xMax),
Mathf.Clamp(GetComponent<Rigidbody2D>().position.y, yMin, yMax)
);
Timer tmr = GameObject.Find("Timer").GetComponent<Timer>();
tmr.BeginTimer();
}
}
catch (System.Exception)
{
}
}
void ApplicationQuit()
{
if (sp != null)
{
{
sp.Close();
}
}
}
}
}
I think the problem may be with how I am referencing the scripts in each other.
In your timer you have a quite strange mixup of Update and Coroutine. Also note that BeginTimer is called exactly once! You also shouldn't "manually" call Update of another component.
I wouldn't use Update at all here. Simply start and stop a Coroutine.
The Timer script should only do the countdown. It doesn't have to know more:
public class Timer : MonoBehaviour
{
public int timeLeft;
public Text countdownText;
private bool timerStarted;
public void BeginTimer(int seconds)
{
// Here you have to decide whether you want to restart a timer
timeLeft = seconds;
// or if you rather want to continue counting down
//if(!timerStarted) timeLeft = seconds;
StartCoroutine(LoseTime());
}
public void StopTimer()
{
StopAllCoroutines();
}
private IEnumerator LoseTime()
{
timerStarted = true;
while (timeLeft > 0)
{
yield return new WaitForSeconds(1);
timeLeft --;
countdownText.text = $"Time Left = {timeLeft}";
}
// Only reached after the timer finished and wasn't interrupted meanwhile
// Using Invoke here is a very good idea since we don't want to interrupt anymore
// if the user lets go of the button(s) now
Invoke(nameof(ChangeLevel), 0.1f);
}
void ChangeLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
In general avoid to use Find at all. If anyhow possible already reference things in the Inspector! If needed you can use Find but only once! What you never want to do is use any of the Find and GetComponent variants repeatedly - rather store the reference the first time and re-use it - and especially not in Update no a per frame basis. They are very expensive calls!
public class PlayerController : MonoBehaviour
{
public float Speed;
public Vector2 height;
// I prefer to use Vector2 for such things
public Vector2 Min;
public Vector2 Max;
public bool buttonPressed = false;
// Already reference these via the Inspector if possible!
public Rigidbody2D Character;
public Timer timer;
public Rigidbody2D _rigidbody;
private SerialPort sp = new SerialPort("\\\\.\\COM4", 9600);
private void Awake()
{
FetchReferences();
}
// This is my "secret" tip for you! Go to the component in the Inspector
// open the ContextMenu and hit FetchReferences
// This already stores the references in the according fields ;)
[ContextMenu("FetchReferences")]
private void FetchReferences()
{
if(!Character)Character = GameObject.FindWithTag("Player"). GetComponent<Rigidbody2D>();
if(!timer) timer = GameObject.Find("Timer").GetComponent<Timer>();
}
private void Start()
{
if (!sp.IsOpen)
{
sp.Open(); // Open
}
sp.ReadTimeout = 1;
}
private void Update()
{
// I wouldn't do the serialport open check here
// your if block simply silently hides the fact that your application
// doesn't work correctly! Rather throw an error!
try
{
string value = sp.ReadLine(); //Read the information
int button = int.Parse(value);
//TODO: Since it isn't clear in your question how you get TWO buttons
//TODO: You will have to change this condition in order to only fire if both
//TODO: buttons are currently pressed!
buttonPressed = button == 0;
if (buttonPressed)
{
Character.AddForce(height, ForceMode2D.Impulse);
// The clamping of a rigidbody should always be done ine FixedUpdate!
// Pass in how many seconds as parameter or make the method
// parameterless and configure a fixed duration via the Inspector of the Timer
timer.BeginTimer(3.0f);
}
else
{
// Maybe stop the timer if condition is not fulfilled ?
timer.StopTimer();
}
}
catch (System.Exception)
{
// You should do something here! At least a Log ...
}
}
private void FixedUpdate()
{
// Here I wasn't sure: Are there actually two different
// Rigidbody2D involved? I would assume you rather wanted to use the Character rigidbody again!
Character.position = new Vector3(Mathf.Clamp(Character.position.x, Min.x, Max.x), Mathf.Clamp(Character.position.y, Min.y, Max.y));
}
// Did you mean OnApplicationQuit here?
private void ApplicationQuit()
{
if (sp != null)
{
{
sp.Close();
}
}
}
}
Typed on smartphone but I hope the idea gets clear
I am trying to change the order of a sorting layer in unity for a 2D game but the below script isn't working for me:
using UnityEngine;
using System.Collections;
public class LevelManager : MonoBehaviour {
public GameObject player;
public SpriteRenderer deadGuy;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (player.transform.position.y < deadGuy.transform.position.y)
{
deadGuy.sortingOrder = 0;
} else
{
deadGuy.sortingOrder = 2;
}
}
}
The objects have been linked in the inspector window unity before running the game.
EDIT
This is now my code:
using UnityEngine;
using System.Collections;
public class LevelManager : MonoBehaviour {
public GameObject player;
public GameObject deadGuy;
public bool belowTheY;
// Use this for initialization
void Start () {
deadGuy.GetComponent<SpriteRenderer>().sortingOrder = 2;
}
// Update is called once per frame
void Update () {
if (player.transform.localPosition.y < deadGuy.transform.localPosition.y)
{
belowTheY = true;
deadGuy.GetComponent<SpriteRenderer>().sortingOrder = 0;
} else
{
belowTheY = false;
deadGuy.GetComponent<SpriteRenderer>().sortingOrder = 2;
}
}
}
bekowTheY is activating if the user goes below deadGuy's Y position so I know the if statement is executing correctly. However the sorting layer is not being changed
It seems that the player object was on sorting order 0 also, so when the deadGuy object was scripted to go to sorting order 0, it was still showing in front due to taking precedence.
I'm simply trying to do basic state managing so that the program does not just run right through but waits for some kind of response of the player. Right now it just jumps over the while loop and the line "Test1" is never printed, it goes straight to "Proceeding". I do have some other scripts running but none of them are using the update loop and shoulnd't impact this one.
This is my first post here so please inform me if there's something I should know about policies etc.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class GameManager : MonoBehaviour {
public static int turn;
public float turnDelay = 1f;
private WaitForSeconds turnWait;
private bool turnOver = false;
void Start ()
{
turnWait = new WaitForSeconds(turnDelay);
turn = 1;
ResetAndSetup();
Debug.Log("Starting");
StartCoroutine(GameLoop());
}
IEnumerator GameLoop()
{
yield return StartCoroutine(RoundFirstPlayer());
Debug.Log("Proceeding");
yield return StartCoroutine(RoundSecondPlayer());
}
private IEnumerator RoundFirstPlayer()
{
while (turnOver = false)
{
if (Input.GetKey(KeyCode.Space))
{
turnOver = true;
Debug.Log("Test1");
}
yield return null;
}
Debug.Log("Test2");
}
private IEnumerator RoundSecondPlayer()
{
yield return null;
}
void ResetAndSetup()
{
ColumnManager.ResetColumns();
}
}
You made small mistake:
Instead
while (turnOver = false)
Use
while (!turnOver)
or
while (turnOver == false)