I just made a png animation sequence that fires when the object is found but on the first loop it takes a lot to load, is there a way to load it faster?
The animation consist of 500 pngs loaded in a sprite the total size of all is about 180Mb, and the code that I am using is very simple:
DefaultTrackableEventHandler.cs:
public SpriteRenderer sr;
public int i = 0;
void Update ()
{
i++;
i = (i > 100) ? 0 : i;
sr.sprite = Resources.Load<Sprite> ("fractal/03_" + i.ToString ("D5"));
}
btw i'm a complete noob on unity programming so please forgive me if i'm missing something obvious, thanks
The problem is that you are loading sprite every frame. It will effect performance. Do the loading once in the Start function and store the result in an array. You can then use that array in the Update function.
const int ANIM_SIZE = 500;
Sprite[] animSprites;
public SpriteRenderer sr;
public int i = 0;
void Start()
{
for (int i = 0; i < ANIM_SIZE; i++)
{
animSprites[i] = Resources.Load<Sprite>("fractal/03_" + i.ToString("D5")) as Sprite;
}
}
void Update()
{
i++;
i = (i > 100) ? 0 : i;
sr.sprite = animSprites[i];
}
Since you are loading many files, you should use Resources.LoadAsync instead. That will let you load the data over frame and prevent any possible freezing when loading the sprites.
const int ANIM_SIZE = 500;
Sprite[] animSprites;
public SpriteRenderer sr;
public int i = 0;
bool isDoneLoading = false;
IEnumerable Start()
{
for (int i = 0; i < ANIM_SIZE; i++)
{
ResourceRequest rs = Resources.LoadAsync<Sprite>("fractal/03_" + i.ToString("D5"));
yield return rs;
animSprites[i] = rs.asset as Sprite;
}
isDoneLoading = true;
}
void Update()
{
if (isDoneLoading)
{
i++;
i = (i > 100) ? 0 : i;
sr.sprite = animSprites[i];
}
}
Finally, don't use any of these solutions mentioned above. They are just there because that's what you asked for. Use the Unity's Animation/Animator's tool to do this. You can learn more about this here.
Related
I am creating a small game with five levels and a timer to see how fast the player can complete all five levels. I have a script that is supposed to take the finished time from the timer and convert it to a PlayerPref so that it can always be saved to the leaderboard. Eash leaderboard entry is its own separate text object, with its own script.
public class ScoreTimer01 : MonoBehaviour
{
public Text theText;
public void Awake()
{
theText.text = GetComponent<Text>().text;
if (PlayerPrefs.HasKey("time0"))
{
theText.text = PlayerPrefs.GetFloat("time0").ToString("mm':'ss'.'ff");
}
else
{
theText.text = "0";
}
}
}
But the leaderboard texts never change, is there something that I am missing?
Here is the timer script:
public class Timer : MonoBehaviour
{
Text text;
float theTime;
public float speed = 1;
public static bool playing;
static List<float> bestTimes = new List<float>();
static int totalScores = 5;
void Awake()
{
LoadTimes();
}
// Start is called before the first frame update
void Start()
{
text = GetComponent<Text>();
playing = true;
}
static public void EndTimer()
{
playing = false;
CheckTime(FinalTime.finalTime);
}
// Update is called once per frame
void Update()
{
if(playing == true)
{
TimerController.theTime += Time.deltaTime * speed;
int minutes = (int)(TimerController.theTime / 60f) % 60;
int seconds = (int)(TimerController.theTime % 60f);
int milliseconds = (int)(TimerController.theTime * 1000f) % 1000;
text.text = "Time: " + minutes.ToString("D2") + ":" + seconds.ToString("D2") + ":" + milliseconds.ToString("D2");
}
}
static public void LoadTimes()
{
for (int i = 0; i < totalScores; i++)
{
string key = "time" + i;
if (PlayerPrefs.HasKey(key))
{
bestTimes.Add(PlayerPrefs.GetFloat(key));
}
}
}
static public void CheckTime(float time)
{
// if there are not enough scores in the list, go ahead and add it
if (bestTimes.Count < totalScores)
{
bestTimes.Add(time);
// make sure the times are in order from highest to lowest
bestTimes.Sort((a, b) => b.CompareTo(a));
SaveTimes();
}
else
{
for (int i = 0; i < bestTimes.Count; i++)
{
// if the time is smaller, insert it
if (time < bestTimes[i])
{
bestTimes.Insert(i, time);
// remove the last item in the list
bestTimes.RemoveAt(bestTimes.Count - 1);
SaveTimes();
break;
}
}
}
}
static public void SaveTimes()
{
for (int i = 0; i < bestTimes.Count; i++)
{
string key = "time" + i;
PlayerPrefs.SetFloat(key, bestTimes[i]);
}
}
void OnDestroy()
{
PlayerPrefs.Save();
}
}
I can't figure out if the PlayerPrefs are not saving correctly or the script to change the text is incorrect. I am very new to C# and unity as a whole, so any help is appreciated.
I'm trying to understand tileSystem build in Unity, and i don't know how to stop animation in AnimatedTiles.
Once animation is started, there is no way i can think of to stop this. I'm working on Unity 2018.3.2f1, but i think that TileSystem is similar in next versions.
Only code in AnimatedTile handling animation is:
public override void GetTileData(Vector3Int location, ITilemap tileMap, ref TileData tileData)
{
tileData.transform = Matrix4x4.identity;
tileData.color = Color.white;
if (m_AnimatedSprites != null && m_AnimatedSprites.Length > 0)
{
tileData.sprite = m_AnimatedSprites[0];
tileData.colliderType = m_TileColliderType;
}
}
public override bool GetTileAnimationData(Vector3Int location, ITilemap tileMap, ref TileAnimationData tileAnimationData)
{
if (m_AnimatedSprites.Length > 0)
{
tileAnimationData.animatedSprites = m_AnimatedSprites;
tileAnimationData.animationSpeed = Random.Range(m_MinSpeed, m_MaxSpeed);
tileAnimationData.animationStartTime = m_AnimationStartTime;
return true;
}
return false;
}
I want to stop animation after some time (like 3 seconds) or after last frame. Any help would be appritiated!
So after some time a got workaround and it looks like this :
public class TileBump : MonoBehaviour
{
public Transform m_GridParent;
public GameObject m_TileMap_Prefab;
public AnimatedTile m_tilePrefabAnimated;
public Tile m_tilePrefabStatic;
private Tilemap map;
void Start()
{
StartCoroutine(EStart());
}
public IEnumerator EStart()
{
GameObject t = Instantiate(m_TileMap_Prefab, m_GridParent);
map = t.GetComponent<Tilemap>();
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
map.SetTile(new Vector3Int(i, j, 0), m_tilePrefabAnimated);
StartCoroutine(Operation(new Vector3Int(i, j, 0)));
yield return new WaitForSeconds(0.3f);
}
}
}
public IEnumerator Operation(Vector3Int x)
{
yield return new WaitForSeconds(m_tilePrefabAnimated.m_AnimatedSprites.Length / m_tilePrefabAnimated.m_AnimationSpeed);
map.SetTile(x, m_tilePrefabStatic);
}
}
BUT. What i understood here is that tiles are not for that. Every tile in TileMap refer to ScriptableObject, so every animation will be same in every frame.
However if someone need this kind of effect, its one way to do it.
I have 4 objects each located in a corner of a square. I wish to move these objects clockwise, 1 position per method call.
With the code I have atm, they all just complete the entire loop on 1 method call instead of moving only one position...
My code so far:
void SwitchPositions()
{
tempPosition1 = parent.transform.GetChild(0).GetComponent<Transform>().position;
tempPosition2 = parent.transform.GetChild(1).GetComponent<Transform>().position;
tempPosition3 = parent.transform.GetChild(2).GetComponent<Transform>().position;
tempPosition4 = parent.transform.GetChild(3).GetComponent<Transform>().position;
parent.transform.GetChild (0).GetComponent<Transform> ().position = tempPosition2;
parent.transform.GetChild (1).GetComponent<Transform> ().position = tempPosition3;
parent.transform.GetChild (2).GetComponent<Transform> ().position = tempPosition4;
parent.transform.GetChild (3).GetComponent<Transform> ().position = tempPosition1;
Debug.Log (tempPosition1);
}
If anyone has any ideas how to fix this or at least explain to me why it-s completing the entire loop in 1 method call...
Thank you!
I am really not sure how your timer works or for that matter anything wrong with your code. But I have used the coroutine where after every two seconds the blocks get switched and it happens continuously. I think this should be somewhere close to what you need.
//Predefined positions where objects to place
public Transform[] Position;
//The objects that will will be swapped in coroutines
public Transform[] ObjectsToMove;
private int ObjectIndex = 0;
private bool startupdate = true;
void Update () {
if(startupdate)
StartCoroutine(SwitchBlocks());
}
IEnumerator SwitchBlocks() {
startupdate = false;
int tempIndex = ObjectIndex;
for(int i = 0; i < ObjectsToMove.Length; i++) {
tempIndex = ObjectIndex + i;
if(tempIndex > ObjectsToMove.Length - 1)
tempIndex -= ObjectsToMove.Length;
ObjectsToMove[i].position = Position[tempIndex].position;
}
ObjectIndex++;
if(ObjectIndex > ObjectsToMove.Length - 1) {
ObjectIndex = 0;
}
yield return new WaitForSeconds(2.0f);
startupdate = true;
yield return null;
}
Hope this helps.
I got some trouble with my unity cardboard app. May some of you guys can help me.
I have build a little Island with Animations and A second island as a main menu.
So when the apps starts, you see the Island from above and the Logo of the App.
When the user pull down the magnet button on side the app will starts another level.
I used this scripts:
http://www.andrewnoske.com/wiki/Unity_-_Detecting_Google_Cardboard_Click
Detecting Google Cardboard Magnetic Button Click - Singleton Implementation
CardboardMagnetSensor.cs and CardboardTriggerControlMono.cs
I created a script in my asset folder(CardboardMagnetSensor.cs) like in the description from Link. Than I created a second script(CardboardTriggerControlMono.cs) like in the discription an dragged it onto my CardboardMain in may Projekt.
The CardboardTriggerControlMono.cs looks like:
using UnityEngine;
using System.Collections;
public class CardboardTriggerControlMono : MonoBehaviour {
public bool magnetDetectionEnabled = true;
void Start() {
CardboardMagnetSensor.SetEnabled(magnetDetectionEnabled);
// Disable screen dimming:
Screen.sleepTimeout = SleepTimeout.NeverSleep;
}
void Update () {
if (!magnetDetectionEnabled) return;
if (CardboardMagnetSensor.CheckIfWasClicked()) {
Debug.Log("Cardboard trigger was just clicked");
Application.LoadLevel(1);
CardboardMagnetSensor.ResetClick();
}
}
}
The CarboardMagnetSensor:
using UnityEngine;
using System.Collections.Generic;
public class CardboardMagnetSensor {
// Constants:
private const int WINDOW_SIZE = 40;
private const int NUM_SEGMENTS = 2;
private const int SEGMENT_SIZE = WINDOW_SIZE / NUM_SEGMENTS;
private const int T1 = 30, T2 = 130;
// Variables:
private static bool wasClicked; // Flips to true once set off.
private static bool sensorEnabled; // Is sensor active.
private static List<Vector3> sensorData; // Keeps magnetic sensor data.
private static float[] offsets; // Offsets used to detect click.
// Call this once at beginning to enable detection.
public static void SetEnabled(bool enabled) {
Reset();
sensorEnabled = enabled;
Input.compass.enabled = sensorEnabled;
}
// Reset variables.
public static void Reset() {
sensorData = new List<Vector3>(WINDOW_SIZE);
offsets = new float[SEGMENT_SIZE];
wasClicked = false;
sensorEnabled = false;
}
// Poll this once every frame to detect when the magnet button was clicked
// and if it was clicked make sure to call "ResetClick()"
// after you've dealt with the action, or it will continue to return true.
public static bool CheckIfWasClicked() {
UpdateData();
return wasClicked;
}
// Call this after you've dealt with a click operation.
public static void ResetClick() {
wasClicked = false;
}
// Updates 'sensorData' and determines if magnet was clicked.
private static void UpdateData() {
Vector3 currentVector = Input.compass.rawVector;
if (currentVector.x == 0 && currentVector.y == 0 && currentVector.z == 0) {
return;
}
if(sensorData.Count >= WINDOW_SIZE) sensorData.RemoveAt(0);
sensorData.Add(currentVector);
// Evaluate model:
if(sensorData.Count < WINDOW_SIZE) return;
float[] means = new float[2];
float[] maximums = new float[2];
float[] minimums = new float[2];
Vector3 baseline = sensorData[sensorData.Count - 1];
for(int i = 0; i < NUM_SEGMENTS; i++) {
int segmentStart = 20 * i;
offsets = ComputeOffsets(segmentStart, baseline);
means[i] = ComputeMean(offsets);
maximums[i] = ComputeMaximum(offsets);
minimums[i] = ComputeMinimum(offsets);
}
float min1 = minimums[0];
float max2 = maximums[1];
// Determine if button was clicked.
if(min1 < T1 && max2 > T2) {
sensorData.Clear();
wasClicked = true; // Set button clicked to true.
// NOTE: 'wasClicked' will now remain true until "ResetClick()" is called.
}
}
private static float[] ComputeOffsets(int start, Vector3 baseline) {
for(int i = 0; i < SEGMENT_SIZE; i++) {
Vector3 point = sensorData[start + i];
Vector3 o = new Vector3(point.x - baseline.x, point.y - baseline.y, point.z - baseline.z);
offsets[i] = o.magnitude;
}
return offsets;
}
private static float ComputeMean(float[] offsets) {
float sum = 0;
foreach(float o in offsets) {
sum += o;
}
return sum / offsets.Length;
}
private static float ComputeMaximum(float[] offsets) {
float max = float.MinValue;
foreach(float o in offsets) {
max = Mathf.Max(o, max);
}
return max;
}
private static float ComputeMinimum(float[] offsets) {
float min = float.MaxValue;
foreach(float o in offsets) {
min = Mathf.Min(o, min);
}
return min;
}
}
And my steps:
http://www.directupload.net/file/d/3887/mtjygjan_jpg.htm
(sorry I´m not able to upload pictures here)
How ever, it wont work. When I start the app and pull down the magnet, nothing happens. May I did something wrong with switching the level over level index?
I use a nexus 4 and 5 for testing the app
Thanks allot and greetz to you!
Phillip
If you are using the Google Cardboard SDK for Unity, it currently has a bug that prevents Unity from seeing the magnet (and gyro, and accelerometer). That is probably why your script is not working. Until the bug is fixed, there is no good workaround, but you can instead use the property Cardboard.CardboardTriggered to detect if the magnet was pulled.
Update for Unity 5: The sensor bug is gone. Cardboard SDK does not block the sensors.
Alright I'm stumped here, I can usually figure out most bugs, through use of Google or common sense but this one has me stumped!
I'm trying to load textures to an array of enemies (Like a row in space invaders) within my struct, however when I go to debug it throws a Null Reference, I know this means that I'm trying to access something that isn't there, I believe it's because i am calling this:
Inside Initialize I call my method:
Targets.setupSprite(this);
This is the actual method from inside the struct:
public void setupSprite(Game1 g)
{
widthFactor = 0.05f;
rowSize = 15;
spriteSpacingY = spriteRectangle.Height + 5;
for (int i = 0; i < rowSize; i++)
{
if (i < 15)
spriteSpacing = 10;
sprites[i].spriteTexture = g.texture;
x = 0 + (i * spriteSpacing);
y = spriteSpacingY;
visible = true;
}
}
Before the Textures can be setup by Content Loader.
It recommends using 'new' but I'm not sure how to use it here.
The line that throws the error as you may have guessed is:
sprites[i].spriteTexture = g.texture;
g.texture is the texture for the enemy and is located in load content.
Any help would be greatly appreciated!
Additional Code:
protected override void Initialize()
{
displayHeight = GraphicsDevice.Viewport.Height;
displayWidth = GraphicsDevice.Viewport.Width;
new TargetRowStruct();
BreadBat.setupSprite(this);
CheeseBall.setupSprite(this);
Targets.setupSprite(this);
base.Initialize();
}
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
texture = this.Content.Load<Texture2D>("Handsome");
swag = this.Content.Load<Texture2D>("swag");
back = this.Content.Load<Texture2D>("Backdrop");
font = this.Content.Load<SpriteFont>("FontA");
backRect.Width = (int)displayWidth;
backRect.Height = (int)displayHeight;
BreadBat.loadTexture(this);
CheeseBall.loadTexture(this);
Targets.loadTexture(this);
BreadBat.scaleSprites(this);
CheeseBall.scaleSprites(this);
Targets.scaleSprites(this);
}
Relevant Code within Targets Struct:
public void scaleSprites(Game1 g)
{
for (int i = 0; i < rowSize; i++)
{
sprites[i].spriteRectangle.Width = (int)((g.displayWidth * sprites[i].widthFactor) + .25f);
sprites[i].aspectRatio = (int)(sprites[i].spriteTexture.Width / sprites[i].spriteTexture.Height);
sprites[i].spriteRectangle.Height = sprites[i].spriteRectangle.Width;
}
}
public void loadTexture(Game1 g)
{
for (int i = 0; i < rowSize; i++)
{
sprites[i].spriteTexture = g.texture;
}
}
public void drawSprite(Game1 g)
{
g.spriteBatch.Draw(spriteTexture, spriteRectangle, Color.White);
for (int i = 0; i < rowSize; i++)
{
if (sprites[i].visible)
{
g.spriteBatch.Draw(sprites[i].spriteTexture, sprites[i].spriteRectangle, Color.White);
}
}
}
public void setupSprite(Game1 g)
{
widthFactor = 0.05f;
rowSize = 15;
spriteSpacingY = spriteRectangle.Height + 5;
for (int i = 0; i < rowSize; i++)
{
if (i < 15)
spriteSpacing = 10;
x = 0 + (i * spriteSpacing);
y = spriteSpacingY;
visible = true;
}
}
P.s It should be noted that the bread and cheese struct which are almost identical (except for being one sprite instead of an array) work fine.
I still suspect, that inside Target struct You don't initialize this sprites[] array;
So, either at the execution time sprites == null or sprites[i] = null;
As I can see, there are three methods invoked on Targets, one is Targets.setupSprite(this); called inside Initialize.
Targets.loadTexture(this); Targets.scaleSprites(this); => those two are called inside LoadContent.
Now, as msdn states, Initialize method is "Called after the Game and GraphicsDevice are created, but before LoadContent."
If, after removing problematic line (which I see You did in the code You added in edit), NullPointerException isn't thrown in relation to sprites[] array, it means, the array is initialized but somewhere after Targets.setupSprite(this);.
If it is thrown, it's probably that You didn't initialize it at all.
The exception is pretty clear, sprites is null. Instantiate it using sprites = new Sprite[100] (or whatever class and size you require) and you're good to go.