Show images faster - c#

I want to show a list of achievements each one with a different image. I've downloaded the images from the server but they appear very slow in my game. Is it a way to show them faster? Thank you in advance.
This is my code:
public GameObject filePrefab;
public GameObject contentRef;
private Texture2D downloadedImages;
public void AchievementsList_Bttn()
{
new GameSparks.Api.Requests.LogEventRequest ()
.SetEventKey ("LISTACHIEVEMENTS")
.Send ((response) => {
if(!response.HasErrors)
{
Debug.Log("List Achivements Loaded Sucessfully...");
GSData scriptData = response.ScriptData;
List<GSData> achievements = scriptData.GetGSDataList("achievements"); //retrieve the array of objects
for (int i = 0; i < achievements.Count; i++)
{
string name = achievements[i].GetString("name");
string description = achievements[i].GetString("description");
int? currency1Award = achievements[i].GetInt("currency1Award");
bool? earned = achievements[i].GetBoolean("earned");
GameObject tempFile = Instantiate (filePrefab, contentRef.transform);
Text tempName = tempFile.transform.GetChild(0).GetComponent<Text>();
Text tempDescription = tempFile.transform.GetChild(1).GetComponent<Text>();
Text tempCurrency1Award = tempFile.transform.GetChild(2).GetComponent<Text>();
RawImage tempImage = tempFile.transform.GetChild(3).GetComponent<RawImage>();
tempName.text = name;
tempDescription.text = description;
tempCurrency1Award.text = currency1Award.ToString();
DownloadtheFiles(name, tempImage);
}
}
});
}
public void DownloadtheFiles(string name, RawImage tempImage)
{
new GetDownloadableRequest()
.SetShortCode(name+"_icon")
.Send((response) => {
if(!response.HasErrors)
{
StartCoroutine(DownloadImages((response.Url), tempImage));
}
});
}
public IEnumerator DownloadImages(string downloadUrl, RawImage tempImage)
{
var www = new WWW(downloadUrl);
yield return www;
downloadedImages = new Texture2D(200, 200);
www.LoadImageIntoTexture(downloadedImages);
tempImage.texture = downloadedImages as Texture;
}
And this is what I want to show:

Minor point, but definitely slow and seen to o often:
string name = achievements[i].GetString("name");
makes a dictionary lookup in the loop. Get the ordinals of the fields outside the loop, pu them into a variable and then use GetString(variable) (with the variable being integer type) - faster lookips.
Use a profiler.
Likely DownloadTheFiles is the bottleneck (do NOT make us do your basic work) and there is no way around this outside of hiding it (i.e. preloading or something else). It is, without you doing the base work and actually doing some debugging and profiling, the only thing that stands out because it does need to really do a network round drop for every image.

Related

Navigation2D.GetSimplePath(from, to) returns unexpected path results;

I'm trying to use Navigation2D.GetSimplePath for my enemy to chase the player once discovered.
using this bit of code I'd expect my enemy to get a Vector2 array containing path info to nav to the player but, my enemy goes in a completely different direction than what I'd expect.
I've tried this:
var from = Enemy.Position;
var to = PlayerRef.Position;
//Nav is my Navigation2d
var paths = Nav.GetSimplePath(from, to);
Enemy.Status.NavPath = new Stack<Vector2>(paths);
for my from and to and but, I've also attempted a lot of conversions
My guess was that my locals need to be converted to the local of the Navigation2d so I tried this:
//Nav is my Navigation2d
var from = Nav.ToLocal(Enemy.GlobalPosition);
var to = Nav.ToLocal(PlayerRef.GlobalPosition);
Since then I've been just bashing my head against the way with converting different global positions to locals of others and using those for the from and to values with similarly off results. I've been looking at this for a really long time (multiple days in my spare time) and I think I'm overlooking something obvious. If possible could anyone provide a second set of eyes and tell me what I've been missing.
Additional note:
This issue only started occurring after my attempt at refactoring. I had this logic all smashed together in a single enemy class till it got to be a pain to maintain. I can provide the original as well.
Original Enemy3.cs Pre-Refactor
My current version of "Chase state" using the FSM pattern:
Enemy using chase state
Here is the full listing of the current file.
using Godot;
using ThemedHorrorJam5.Scripts.Enum;
using ThemedHorrorJam5.Scripts.Patterns.StateMachine;
using ThemedHorrorJam5.Scripts.GDUtils;
using System.Collections.Generic;
using System.Linq;
namespace ThemedHorrorJam5.Entities
{
public class ChaseEnemyState : State
{
private Navigation2D? GetLevelNavigation()
{
var nodeTuples = Enemy.GetTree().GetNavigation2dNodes();
if (nodeTuples.Item1) return nodeTuples.Item2[0];
return null;
}
private Navigation2D Nav { get; set; }
private EnemyV4 Enemy { get; set; }
private PlayerV2 PlayerRef { get; set; }
public ChaseEnemyState(EnemyV4 enemy)
{
this.Name = EnemyBehaviorStates.ChasePlayer.GetDescription();
Enemy = enemy;
(var hasPlayer, PlayerRef) = Enemy.GetTree().GetPlayerNode();
if (!hasPlayer)
{
Logger.Error("Player ref not found on scene tree");
}
(var hasNav, var navNodes) = Enemy.GetTree().GetNavigation2dNodes();
if (hasNav && navNodes != null)
{
Nav = navNodes[0];
}
this.OnEnter += () => this.Logger.Debug("ChaseEnemyState OnEnter called");
this.OnExit += () => this.Logger.Debug("ChaseEnemyState Exit called");
this.OnFrame += ChasePlayer;
}
private void ChasePlayer(float delta)
{
if (Enemy.IsDebugging && Enemy.HasNode("Line2D"))
{
Enemy.Status.Line = (Line2D)Enemy.GetNode("Line2D");
}
if (Nav!=null)
{
//Enemy.Status.Navigation2D = GetLevelNavigation();
//var nav = (Navigation2D)Enemy.Owner.GetNode("Navigation2D");
//Enemy.Status.Navigation2D = (Navigation2D)Enemy.Owner.GetNode("Navigation2D");
//var from = Enemy.Position;
var from = Nav.ToLocal(Enemy.GlobalPosition);
//var from = Enemy.GlobalPosition;
var to = Nav.ToLocal(PlayerRef.GlobalPosition);
//var to = PlayerRef.Position;
//var to = Enemy.Status.Target.Position;
//var to = Enemy.Status.Target.ToLocal(Enemy.Status.Target.Position);
//Enemy.DrawLine(from, to, new Color(255, 255, 255), 3);
//var paths = Enemy.Status.Navigation2D.GetSimplePath(from, to);
var paths = Nav.GetSimplePath(from, to);
Enemy.Status.NavPath = new Stack<Vector2>(paths);
if (Enemy.Status.Line != null)
{
Enemy.Status.Line.Points = Enemy.Status.NavPath.ToArray();
}
var distance_to_walk = Enemy.MoveSpeed * delta;
while (distance_to_walk > 0f && Enemy.Status.NavPath.Count > 0f)
{
var distance_to_next_point = Enemy.Position.DistanceTo(Enemy.Status.NavPath.Peek());
if (distance_to_walk <= distance_to_next_point)
{
var newPosition = Enemy.Position.DirectionTo(Enemy.Status.NavPath.Peek()) * distance_to_walk;
Enemy.Status.VisionManager.UpdateFacingDirection(newPosition.Normalized());
Enemy.Position += newPosition;
}
else
{
var newPosition = Enemy.Status.NavPath.Pop();
Enemy.Status.VisionManager.UpdateFacingDirection(newPosition.Normalized());
if (Enemy.GetSlideCount() > 0)
{
Enemy.HandleMovableObstacleCollision(newPosition);
}
Enemy.Position = newPosition;
}
distance_to_walk -= distance_to_next_point;
}
if (Enemy.IsDebugging)
{
Enemy.Status.DebugLabel.Text =
#$"
|-----------------------------------------------------------
| Enemy Global Position: {Enemy.GlobalPosition}
| Enemy Local Position: {Enemy.Position}
|----------------------------------------------------------
| Target Global Position: {Enemy.Status.Target.GlobalPosition}
| Target Local Position: {Enemy.Status.Target.Position}
|-----------------------------------------------------------
| From {from}
| To {to}
|-----------------------------------------------------------";
}
}
else
{
Logger.Error("Navigation2D not found");
}
if (Enemy.Status.CurrentCoolDownCounter > 0)
{
Enemy.Status.CurrentCoolDownCounter -= delta;
}
}
}
}
Some images to help show what I'm seeing. Player is green and the enemy is blue. From and To correspond to the values of the variables. The yellow is the sight cone for the enemy and is pointing out in the direction the enemy is walking. Target is an alias for player and the red line is a Line2d I've been using to draw the enemy's path.
//Nav is my Navigation2d
var from = Enemy.Position;
var to = PlayerRef.lPosition;
Second attempt results with :
//Nav is my Navigation2d
var from = Nav.ToLocal(Enemy.GlobalPosition);
var to = Nav.ToLocal(PlayerRef.GlobalPosition);
Any insight at all would be helpful as to what I'm doing wrong.
I can only guess this began failing due to the nodes being moved around. Either the order in the scene tree, or the Navigation2D position. I'm guessing that because the code was previously only using local positions. Meaning that their local coordinates used to match, but they no longer do.
Anyway, this code is correct:
var from = Nav.ToLocal(Enemy.GlobalPosition);
var to = Nav.ToLocal(PlayerRef.GlobalPosition);
And this is progress. You had fragile code before (it depended on the coincidence that the local coordinates matched). So this will ultimately be good.
I believe the issue you are facing comes from here:
Enemy.Position.DistanceTo(Enemy.Status.NavPath.Peek());
Which would be be correct if their local coordinates matched, but they don't. So we need to convert the result we get here.
If you are going to work on the enemy local coordinates, you need to convert again:
Enemy.Position.DistanceTo(Enemy.ToLocal(Nav.ToGlobal(Enemy.Status.NavPath.Peek())));
However, I'd suggest to work in global coordinates instead (since you can write to Enemy.GlobalPosition instead of Enemy.Position):
Enemy.GlobalPosition.DistanceTo(Nav.ToGlobal(Enemy.Status.NavPath.Peek()));
You would have to do the same change in other places in your code.
Let us see here:
var newPosition = Enemy.Position.DirectionTo(Enemy.Status.NavPath.Peek()) * distance_to_walk;
Enemy.Status.VisionManager.UpdateFacingDirection(newPosition.Normalized());
Wait, that isn't a position is it? That's a displacement. Don't mix things up.
Well, the question is what coordinates does UpdateFacingDirection expect.
You also have this code:
var newPosition = Enemy.Status.NavPath.Pop();
Enemy.Status.VisionManager.UpdateFacingDirection(newPosition.Normalized());
Sure a position normalized is a direction… From the origin. But which origin? In this case it is the origin of the Navigation2D…
So, I have searched for UpdateFacingDirection in your code. I found a couple implementations which look like this:
public void UpdateFacingDirection(Vector2 newVelocity)
{
this.Rotation = this.Position.AngleToPoint(newVelocity);
}
See? This makes no sense. The code claims to be taking the angle to go from a point to a velocity. I'll believe your instruction and not your naming. In which case UpdateFacingDirection takes a position in local coordinates.
I have also looked for HandleMovableObstacleCollision, and I found this implementation:
public void HandleMovableObstacleCollision(Vector2 motion)
{
this.PrintCaller();
motion = motion.Normalized();
if (GetSlideCollision(0).Collider is PushBlock box && box.CanBePushed)
{
box.Push(PushSpeed * motion);
}
}
So apparently this only cares about the direction of the argument. Now, this is the Push I found:
public void Push(Vector2 velocity)
{
MoveAndSlide(velocity);
}
So that is global coordinates. Meaning that HandleMovableObstacleCollision takes a direction in global coordinates.
Ok, I think I can rewrite:
var from = Nav.ToLocal(Enemy.GlobalPosition);
var to = Nav.ToLocal(PlayerRef.GlobalPosition);
var paths = Nav.GetSimplePath(from, to);
// …
var distance_to_walk = Enemy.MoveSpeed * delta;
while (distance_to_walk > 0f && paths.Count > 0f)
{
var next_point = Nav.ToGlobal(paths.Peek());
var global_direction = Enemy.GlobalPosition.DirectionTo(next_point);
var global_distance = Enemy.GlobalPosition.DistanceTo(next_point);
if (distance_to_walk <= global_distance)
{
var global_displacement = global_direction * distance_to_walk;
var global_new_position = Enemy.GlobalPosition + global_displacement;
var local_new_position = Enemy.ToLocal(global_new_position);
Enemy.Status.VisionManager.UpdateFacingDirection(local_new_position);
Enemy.GlobalPosition = global_new_position;
}
else
{
_ = paths.Pop();
// var global_displacement = next_point - Enemy.GlobalPosition;
var global_new_position = next_point;
var local_new_position = Enemy.ToLocal(global_new_position);
Enemy.Status.VisionManager.UpdateFacingDirection(local_new_position);
if (Enemy.GetSlideCount() > 0)
{
Enemy.HandleMovableObstacleCollision(global_direction);
}
Enemy.GlobalPosition = global_new_position;
}
distance_to_walk -= global_distance;
}
I don't know what are you storing Enemy.Status.NavPath for, if you are calling navigation each frame. So I removed that from the code above. If you do need it, add it back. Similarly, I don't know what to make of Enemy.Status.Line.Points. My guess is that is only for debug, and it should be in local coordinates of the Line2D, if you need it, perhaps you can use the global transforms to convert them (with Xform and XformInv).
About the line:
_ = paths.Pop();
Is a discard. We don't really need a discard. You can simply call Pop:
paths.Pop();
The discard - in this case - is just meant to indicate that we intentionally do not use the returned value. We don't need to, because we already got it from Peek.
By the way, in the second branch I added comment with global_displacement, you will see what you would need that line for below.
There is something else that bothers me:
if (Enemy.GetSlideCount() > 0)
{
Enemy.HandleMovableObstacleCollision(/*…*/);
}
What slide count? In fact, in HandleMovableObstacleCollision I see you use GetSlideCollision, but what slide collisions? If you don't use MoveAndSlide or similar on the Enemy? Instead I see you write Enemy.Position (which I changed to Enemy.GlobalPosition)?
Let us use MoveAndSlide. The catch is that it does not take a displacement, nor a position, it takes a velocity. So we will pass global_displacement / delta (remember that velocity is displacement divided by delta time). So instead of this:
Enemy.GlobalPosition = global_new_position;
Do this:
Enemy.MoveAndSlide(global_displacement / delta);
Also, don't you want to do that before checking Enemy.GetSlideCount()? Well, I don't know. You can finish figuring this out.

Trying to create an interruptible dialogue system, with subtitles, using coroutines

I am currently working on a game where to characters are having a conversation. There are a few dialogue options, but the conversation trees are quite simple. A key aspect is that the player is able to interrupt the other party mid-sentence and change the course of the conversation.
To preface, I have learned all I know from youtube and I've been getting by on increasingly complex if-statements, so I'm trying something new here.
So I did my first attempt using what I know: Invoke and if-statements.
public class SubtitleSystem : MonoBehaviour
{
public float subtitleTimeBuffer;
public string[] dialogue;
public string[] specialDialogue;
public float[] subTiming;
public float[] specialSubTiming;
public AudioClip[] diaClips;
public Text sub;
AudioSource player;
int subNum;
int specialSubNum;
// Start is called before the first frame update
void Start()
{
player = gameObject.GetComponent<AudioSource>();
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.E))
{
if (sub.enabled == false) {
SubSystem();
}
}
}
void SubSystem()
{
if(subNum < dialogue.Length)
{
if (subNum != 1)
{
player.clip = diaClips[subNum];
player.Play();
sub.text = dialogue[subNum];
sub.enabled = true;
Invoke("DisableText", subTiming[subNum] + subtitleTimeBuffer);
subNum++;
}
else if (subNum == 1)
{
player.clip = diaClips[subNum];
player.Play();
sub.text = specialDialogue[specialSubNum];
sub.enabled = true;
Invoke("DisableText", subTiming[subNum] + subtitleTimeBuffer);
Invoke("SpecialSub", specialSubTiming[specialSubNum]);
}
} else
{
Debug.Log("No more dialogue");
}
}
void DisableText()
{
sub.enabled = false;
}
void SpecialSub()
{
sub.text = dialogue[subNum];
subNum++;
}
This was functional, though clunky and very work intensive for the person that has to find the individual timing for each line of dialogue and manually break up a subtitle line if it was too long.
Another big problem was that it was impossible to fully interrupt dialogue, because Invoke was called and would run regardless of what I pressed. Maybe I could add some kind of bool condition to prevent that, but the project I'm working on will have a few hundred lines of dialogue, so I need to come up with something where I don't have to type in every line in the inspector and find the manual timing.
This is where it becomes murky for me as I am unfamiliar with a lot of these methods.
An obvious solution to the Invoke problem would be to use Coroutines. These can be interrupted and I could even have a check for input inside a loop to let a player interrupt.
IEnumerator SubtitleRoutine()
{
while (DialogueIsPlaying)
{
//Display Subtitles
if(Input.GetButton("Interrupt button"){
//interrupt
}
yield return null;
}
//Wait for next piece of dialogue
}
Something like that is what I'm imagining.
The next problem is tying dialogue to some kind of system so I can pull the correct piece of audio and display the correct subtitles. In my first attempt this was simple enough because the four pieces of test audio I created were short sentences, but if a character speaks for longer it would be tedious to break up the dialogue manually.
Alternatively I thought about breaking the audio files up into "subtitle-length" so every audio file had a subtitle string directly associated with it, but this seems inefficient and troublesome if dialogue needs to change down the line.
So I thought if I could somehow create a class, that contained all the information needed, then my coroutine could pull in the correct dialogue using it's id (perhaps an integer) and plug in all the information from the object into my coroutine.
So something like this:
public class dialogue
{
//Int ID number
//Audiofile
//Who is speaking
//Length
//Subtitle String 1
//Subtitle String 2
//Subtitle String 3
// etc
}
IEnumerator SubtitleRoutine(dialogue)
{
while (DialogueIsPlaying)
{
//Display Subtitles - divide number of subtitle string by Length and display each for result.
if (Input.GetButton("Interrupt button"){
//interrupt audio and subtitles - stop the coroutine
//set correct dialogue Int ID for next correct piece of dialogue and start coroutine with new dialogue playing.
}
yield return null;
}
//Wait for next piece of dialogue
}
Though this is all outside of what I know, from what I've been able to read and understand, this seems like it might work.
So my question is:
Is this approach going to work?
If so, where should I look for ressources and help to teach me how?
If not, what methods should I look at instead?
Thank you so much for your help!
So what I understand is that you want to make an interruptable dialog system. You chose the coroutine approach which is a great choice, but you're not using it to its full potential. When you use StartCoroutine(IEnumerator _enumerator); you'll get a coroutine class back. If you store it, you can later use StopCoroutine(Coroutine _routine); to stop it. So you won't have to use a while loop or if statements to check interrupting.
Hope this will help you. If it doesn't I'll send some code.
After receiving some help from a coding mentor I found a system that works using a custom class as a datatype and coroutines to display subtitles with correct timing.
The names of the variables are in Danish but the code works in Unity without issues.
using System.Collections.Generic;
using UnityEngine;
public class Undertekster
{
public int id;
public AudioClip audioFile;
public float length;
public string[] subtitles;
public bool isMonk;
public SubSystem subsys;
public Undertekster(int id, int dialogClipNummer, float length, string[] subtitles, bool isMonk, SubSystem subsys)
{
this.subsys = subsys;
this.id = id;
this.audioFile = subsys.dialogClip[dialogClipNummer];
this.length = length;
this.subtitles = subtitles;
this.isMonk = isMonk;
}
}
Notice that use another script when constructing the class to make use of Monobehavior. That way I can assign the correct audiofile to each line of dialogue using an array created in the inspector. The proper way would probably be to look for the file somehow, but that's beyond me.
Next is the subtitle system. For demonstration you hit space in-game to start dialogue and hit F to interrupt. The "subtitles" are Debug.log in the console, but you can easily tie them to a Text object in the UI.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SubSystem : MonoBehaviour
{
[Header("DialogClip")]
public AudioClip[] dialogClip;
[Header("Indstillinger")]
public float subtitleBuffer;
public AudioSource munk;
public AudioSource salamander;
List<Undertekster> alleUndertekster = new List<Undertekster>();
int currentDialogueNumber;
Undertekster currentDia;
float timeDivided;
void Start()
{
currentDialogueNumber = 1;
LoadDialog();
}
public void LoadDialog()
{
alleUndertekster.Add(new Undertekster(1, 0, dialogClip[0].length, new string[] { "Kan du huske mig?" }, true, this));
alleUndertekster.Add(new Undertekster(2, 1, dialogClip[1].length, new string[] { "Øh...", "Lidt..." }, false, this));
alleUndertekster.Add(new Undertekster(3, 2, dialogClip[2].length, new string[] { "Jeg er din nabo din idiot!" }, true, this));
alleUndertekster.Add(new Undertekster(4, 3, dialogClip[3].length, new string[] { "Shit!" }, false, this));
}
IEnumerator PlayNextDialogue()
{
int count = 0;
while (munk.isPlaying || salamander.isPlaying)
{
ShowSubtitle(count);
yield return new WaitForSeconds(timeDivided + subtitleBuffer);
count++;
yield return null;
}
//yield return new WaitForSeconds(subtitleBuffer);
currentDialogueNumber++;
Debug.Log("Coroutine is stopped and the current dialogue num is " + currentDialogueNumber);
StopCoroutine(PlayNextDialogue());
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.F))
{
InterruptDialogue();
}
if (Input.GetKeyDown(KeyCode.Space))
{
StartDialogue();
}
}
public void StartDialogue()
{
currentDia = alleUndertekster.Find(x => x.id == currentDialogueNumber);
timeDivided = currentDia.length / currentDia.subtitles.Length;
if (currentDia.isMonk)
{
munk.clip = currentDia.audioFile;
munk.Play();
} else if (!currentDia.isMonk)
{
salamander.clip = currentDia.audioFile;
salamander.Play();
}
StartCoroutine(PlayNextDialogue());
}
public void InterruptDialogue() {
StopCoroutine(PlayNextDialogue());
munk.Stop();
salamander.Stop();
currentDialogueNumber++;
StartDialogue();
}
public void ShowSubtitle(int i)
{
if(i <= currentDia.subtitles.Length - 1)
{
Debug.Log(currentDia.subtitles[i]);
} else
{
return;
}
}
}
I chose to put all the dialogue classes into a list so it was easily searchable for the id-numbers. It might have been better to use a Dictionary, but this worked for me and that was good it enough for this project.
With this system, my manuscript writer can put in every line of dialogue with its associated audioclip in the LoadDialog() function and determine who is speaking and in how many pieces the subtitles should be broken into to fit on the screen. They are then displayed one after the other while the audio is playing.
This probably isn't the best solution in the world, but I hope it works for whoever might need it - plus I learned a ton.

Gameobject destroy fails inside destructor

I have an object, which contains a list of GameObjects. I wish to destroy all of these GameObjects in its destructor.
However, when I attempt to call GameObject.Destroy() from inside the destructor, it seems to halt execution (the line after GameObject.Destroy() never executes, but the line before it does)
If i copy and paste exactly the same code into a function called not_a_destructor() and call that instead, it works perfectly. What gives? I've got it working, but I would really like to understand what's going on.
Destructor and not_a_destructor() code:
// Destructor DOES NOT work
~MoveAction(){
for(int i = 0; i < arrows.Count; i++){
Debug.Log("wasd");
GameObject.Destroy(arrows[i]);
Debug.Log("asdf");
}
}
// Identical code, calling not_a_destructor() works perfectly
public void not_a_destructor(){
for(int i = 0; i < arrows.Count; i++){
Debug.Log("PRETEND DESTRUCTOR!");
GameObject.Destroy(arrows[i]);
Debug.Log("GameObject destroyed successfully");
}
}
As requested in comments, a full copy of the class:
public class Action
{
public int type;
public string debug_string;
public GameObject ui_pill; // Only present for Actions created on the client
}
public class MoveAction : Action
{
public int type = ActionType.MOVE;
public MapHex origin;
public List<MapHex> route; // Intermediate hexes travelled through during the move (includes target_hex)
public Fleet fleet;
private List<GameObject> arrows = new List<GameObject>(); // Arrows for the graphical representation of the pending move on the tactical map
public MapHex target_hex {
get {
return route[route.Count - 1];
}
}
public string debug_string {
get {
return "MOVE ACTION WITH FLEET: " + fleet.name;
}
}
public MoveAction(Fleet _fleet, List<MapHex> _route){
fleet = _fleet;
route = _route;
origin = fleet.planned_position;
update_arrows_from_route();
}
public void update_arrows_from_route(){
Material default_material = new Material(Shader.Find("Sprites/Default"));
// Create one arrow for every hex we will pass through.
MapHex last = fleet.planned_position;
foreach (MapHex hex in route){
// Create arrow from last to hex
GameObject arrow_gameobj = new GameObject();
arrow_gameobj.name = "move_order_arrow";
LineRenderer line_renderer = arrow_gameobj.AddComponent<LineRenderer>();
line_renderer.material = default_material;
line_renderer.SetColors(fleet.owner.color, fleet.owner.color);
line_renderer.positionCount = 2;
arrow_gameobj.layer = layers.tactical_map;
Vector3[] line_points = new Vector3[]{last.position, hex.position};
line_renderer.SetPositions(line_points);
line_renderer.startWidth = 0.1f;
line_renderer.endWidth = 0.1f;
arrows.Add(arrow_gameobj);
last = hex;
}
}
public void not_a_destructor(){
for(int i = 0; i < arrows.Count; i++){
Debug.Log("PRETEND DESTRUCTOR!");
GameObject.Destroy(arrows[i]);
Debug.Log("GameObject destroyed successfully");
}
}
~MoveAction(){
for(int i = 0; i < arrows.Count; i++){
Debug.Log("wasd");
GameObject.Destroy(arrows[i]);
Debug.Log("asdf");
}
}
Its probable best to use more of Unity and less of C#, there is a good callback called OnDestroy() which would be a fine place to destroy all the arrows. If execution of your unity code depends on running a finalizer on something, this is a very strong code smell.
Unless you are using IO in a way that REQUIRES an action to happen in a finalizer (possibly things like releasing an IO resource), its best to leave them empty, and put Unity code inside Unity callbacks

Unity Changing material color on multiple gameobjects

I made a right click menu and i want to make objects change material color while i have my mouse on a button in that menu.
This is the code:
Color[] startCo;
public void OnPointerEnter(PointerEventData eventData)
{
GameObject[] objects = GameObject.FindGameObjectsWithTag(myMenu.selected.title);
for (int i = 0; i < startCo.Length; i++)
{
startCo[i] = objects[i].gameObject.GetComponent<MeshRenderer>().material.color;
}
foreach (GameObject obj in objects)
{
obj.gameObject.GetComponent<MeshRenderer>().material.color = Color.red;
}
}
public void OnPointerExit(PointerEventData eventData)
{
GameObject[] objects = GameObject.FindGameObjectsWithTag(myMenu.selected.title);
for (int i = 0; i < objects.Length; i++)
{
objects[i].gameObject.GetComponent<MeshRenderer>().material.color = startCo[i];
}
}
With first for loop it does not work at all, but without it, when I put my mouse on the button, it makes material colors red, but it won't change it back to orginal.
My question is, is there any better way to save original colors with using foreach?
Try this version. It basically does exactly the same thing as your version, but makes sure to initialize the color array before using it (which was probably your main issue). It also keeps a copy of the list of objects whose colors were replaced, which is important in case new objects with a matching tag are created, or existing one's deleted. Finally, it adds a few safeguards to make it more robust in case you want to use ReplaceColors() and RestoreColors() in other places, too.
GameObject[] objectsWithReplacedColors;
Color[] originalColors;
public void OnPointerEnter(PointerEventData eventData)
{
ReplaceColors(GameObject.FindGameObjectsWithTag(myMenu.selected.title), Color.red);
}
public void OnPointerExit(PointerEventData eventData)
{
RestoreColors();
}
private void ReplaceColors(GameObject[] forObjects, Color withColor)
{
if (objectsWithReplacedColors != null) // if there are already objects with replaced colors, we have to restore those first, or their original color would be lost
RestoreColors();
objectsWithReplacedColors = forObjects;
originalColors = new Color[objectsWithReplacedColors.Length];
for (int i = 0; i < objectsWithReplacedColors.Length; i++)
{
originalColors[i] = objects[i].GetComponent<MeshRenderer>().material.color;
objectsWithReplacedColors[i].GetComponent<MeshRenderer>().material.color = withColor;
}
}
private void RestoreColors()
{
if (objectsWithReplacedColors == null)
return;
for (int i = 0; i < objectsWithReplacedColors.Length; i++)
{
if (objectsWithReplacedColors[i]) // check if the objects still exists (it may have been deleted since its color was replaced)
objectsWithReplacedColors[i].GetComponent<MeshRenderer>().material.color = originalColors[i];
}
objectsWithReplacedColors = null;
originalColors = null;
}
Well my guess is you are finding objects every time these methods are called using GameObject.FindGameObjectsWithTag and i am pretty sure the order of these objects returned by FindGameObjectsWithTag is not specified so it can change ever time you call this method. This issue ends up giving you different original colors for different objects. My suggestion would be getting objectsin the Startonce and assigning colors to the array. Then use this array in OnPointerExitfunction.
Other than that your code and logic seems fine to me.

Unity - Set material from Downloaded image instead of resources

I can successfully load a material saved in my Unity project using the code below:
RenderSettings.mat1 = (Material)Resources.Load ("images/img1.jpg", typeof(Material));
However, I am now trying to load an external image by downloading it.
Texture2D imgDownloaded;
string url = "http://www.intrawallpaper.com/static/images/1968081.jpg";
void Start()
{
StartCoroutine(getImg());
fucntionx ();
}
public void functionx()
{
RenderSettings.mat1 = (Material)imgDownloaded;
}
IEnumerator getImg()
{
yield return 0;
WWW dl = new WWW(url);
yield return dl;
imgDownloaded = dl.texture;
}
However, I get the message that I cannot convert from Texture2D to Material .
Is there any way to fix this?
Try:
yourMaterial.mainTexture = yourTexture;
A material consists of many textures, so naturally you can't convert between them.

Categories

Resources