Jump to content
Unity Insider Forum
Sign in to follow this  
Garzec

Circle Sprite in Farbabschnitte aufteilen

Recommended Posts

Hallo,

ich habe einen Kreis (Sprite), der sich durchgehend dreht.

private void Update()
{
    transform.Rotate(0, 0, 10 * Time.deltaTime);
}

Die Komponente auf dem Kreis erhält zu Beginn ein Array vom Typ <Color>. Ich möchte diesen Kreis in gleichmäßige Stücke mit diesen Farben aufteilen

private void SetColors(Color[] colors)
{
    // das Sprite mit Farben einteilen
}

Wenn ich beispielsweise die Farben rot, blau, grün, gelb im Array habe sollte der Kreis so aussehen

vECit.png

Dabei ist die Anzahl der Farben unbegrenzt.

Der Kreis selbst hat einen Trigger, wird dieser ausgelöst, soll der Kreis wissen, welche Farbe "getroffen" wurde.

private void OnTriggerEnter2D(Collider2D col)
{
    if (col.gameObject == ball)
    {
        // Color hitColor = die Farbe, die getroffen wurde
    }
}

Der Kreis wird immer von oben getroffen ( x-Koordinate ist 0 )

Dz2te.png

 

Meine Fragen lauten:

1. Wie kann ich den Kreis passend einfärben?

2. Wie kann ich herausfinden, welche Farbe momentan "oben" war.

 

Meine Überlegung zur Farbeinteilung:

Die Einteilung der Farben dient ja nur der Optik, also nicht der Logik selbst. Ist es möglich, einen Shader über diesen Kreis zu legen und die Farben zu simulieren?

 

Meine Überlegung zur Farbabfrage:

Da die Farben wahrscheinlich im Uhrzeigersinn eingeteilt werden und ich die Anzahl der Farben kenne kann ich ja folgendes aufstellen..

Ich habe n Farben. Jede Fläche nimmt also (360 / n) Grad ein. Wenn ich den Kreis wie oben beschrieben rotiere, kann ich doch bestimmt berechnen, wann sich welche Farbe wo befindet?

Share this post


Link to post
Share on other sites

Natürlich geht das mit nem Shader. Das Problem ist ähnlich wie ein Schild-Shader mit Impact-Effect. Gibt Tonnenweise tutorials davon auf YT.

Einfach 2 vector-Arrays erstellen. eins für die Farbe, eins für die Position. Da Arrays in Shadern nicht dymanisch sein können, musst du ein MAximum definieren und die Füll-Menge mit übergeben.

Share this post


Link to post
Share on other sites

Also die Farbberechnung habe ich schon einmal. Für jede Farbe, die der Kreis haben soll, erzeuge ich mir folgendes Objekt 

public class RouletteColor
{
    public RouletteColor(int colorIndex, Color color, int maxColors)
    {
        float step = 360 / maxColors;
        rangeMin = step * colorIndex;
        rangeMax = step * (colorIndex + 1);
        currentColor = color;
    }

    private float rangeMin;
    public float RangeMin { get { return rangeMin; } }

    private float rangeMax;
    public float RangeMax { get { return rangeMax; } }

    private Color currentColor;
    public Color CurrentColor { get { return currentColor; } }
}

und füge dieses Objekt der Liste hinzu. Wenn ich die aktuelle Farbe abfragen möchte, frage ich die aktuelle Rotation ab

    private Color GetCurrentColor()
    {
        float currentRotation = transform.eulerAngles.z;
        RouletteColor rouletteColor = rouletteColors
                                            .Where(x => x.RangeMin < currentRotation && x.RangeMax >= currentRotation)
                                            .First();
        return rouletteColor.CurrentColor;
    }

Fehlt nun noch der Shader, den ich dann wohl mit dem ShaderGraphen bauen werde.

Share this post


Link to post
Share on other sites

Das ist zwar nicht falsch, aber etwas umständlich..

public Color[] colors;

private Color GetCurrentColor(float rot)
{
 float currentRotation = rot % 360;
 float anglePerColor = 360f / colors.Length;
 int ColorIndex = (int)(currentRotation / anglePerColor);
 return colors[ColorIndex];
}

 

Und um den Kreis zu malen kannst du natürlich einen Shader verwenden.. aber wie thewhiteshadow schon geschrieben hat wird es nicht ganz einfach eine dynamische Anzahl an Kreissektoren mit einem Shader zu generieren..

Ich würde einfach am Anfang die Kreistextur einmal generieren lassen..

private Texture2D generateCircleTexture(int size)
{
 Color[] pixels = new Color[size * size];
 Vector2 center = new Vector2(size / 2f, size / 2f);
 float anglePerColor = 360f / colors.Length;
  
 for (int y = 0; y < size; y++)
 {
  for (int x = 0; x < size; x++)
  {
   Vector2 dir = new Vector2(x + .5f, y + .5f) - center;
   float angle = -Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg + 90;
   angle = angle < 0 ? 360 + angle : angle;
   int colorIndex = Mathf.Min((int)(angle / anglePerColor), colors.Length - 1);
   pixels[x + y * size] = dir.magnitude < size * .5f ? colors[colorIndex] : Color.clear;
  }
 }
  
 Texture2D tex = new Texture2D(size, size);
 tex.SetPixels(pixels);
 tex.Apply();
 return tex;
}

 

Share this post


Link to post
Share on other sites

@Mr 3d erstmal vielen Dank für deine Lösung :) Trotzdem habe ich noch Fragen dazu:

Ich finde es clever, die aktuelle Farbe direkt zu berechnen,  ohne Hilfsobjekte. Aber was ist der 

private Color GetCurrentColor(float rot)

rot Parameter bei dir? 

transform.eulerAngles.z

? Ebenfalls bin ich dir für die Textur dankbar. Ich wusste gar nicht, dass sowas möglich ist. Ich dachte entweder trickse ich mit dem Canvas oder ein Shader muss her. Ebenso weiß ich noch nicht, wie ich deine Methode anzuwenden habe.

Ist der size Parameter der Radius? Oder der Durchmesser? Und wie wende ich diese Methode, die eine Textur zurückliefert, auf das Element an? Ich habe mal

GetComponent<Renderer>().material.mainTexture = generateCircleTexture(5);

probiert, aber da hat sich am Sprite nichts getan.

Share this post


Link to post
Share on other sites

Ja, 'rot' war kurz für 'rotation' bzw. die Gradzahl um die sich die Scheibe gedreht hat. Also in deinem Fall 'transform.eulerAngles.z'

 

Der 'size' Parameter steht für die Höhe&Breite in Pixeln des Kreises. Ist vtl. nicht so sinnvoll das als Parameter zu übergeben.

Um die Textur als Sprite verwenden zu können, musst du erst daraus ein Sprite generieren.. (https://docs.unity3d.com/ScriptReference/Sprite.Create.html)

etwa so:

Texture2D tex = generateCircleTexture(512);
Sprite circleSprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(.5f, .5f), 512); //letzter Parameter sind Pixel pro Unit, also ist das Sprite 1 Unit groß

Dann musst du von deinem Sprite Objekt auf die Image Componente zugreifen und das neue Sprite setzen.. (nicht vergessen 'using UnityEngine.UI')

using UnityEngine.UI;
public Image GluecksradGraphic;
//--
GluecksradGraphic.sprite = circleSprite;

// edit

Du benutzt ja gar nicht UI.. 

Dann musst du auf die SpriteRenderer Componente zugreifen und den 'sprite' Parameter ändern, nicht die Textur vom Material..

GetComponent<SpriteRenderer>().sprite = circleSprite;

 

Share this post


Link to post
Share on other sites
vor 29 Minuten schrieb Garzec:

Ich wusste gar nicht, dass sowas möglich ist. Ich dachte entweder trickse ich mit dem Canvas oder ein Shader muss her.

Kannst du trotzdem nachvollziehen was die Methode macht?

Im 'pixels' Array sind die Farben aller Pixel gespeichert. Dann wird jeder Pixel angeschaut und berechnet wo er im Kreis liegt und dann entsprechend eingefärbt.

Share this post


Link to post
Share on other sites

@Mr 3d

Ich kann teilweise nachvollziehen, was sie macht. Also für den genauen Ablauf fehlt mir das mathematische Wissen. Aber du gehst per Bogenmaß über die "Gradflächen" und färbst dort die Pixel farbig ein.

Zwei Fragen hätte ich noch, 

- wenn ich als Parameter für die Größe ebenfalls 512 mitgebe, werden die Kanten ordentlicher / feiner. Der Kreis wird dadurch aber natürlich auch größer. Wo wird denn die Größe der Textur innerhalb von GenerateCircleTexture bestimmt?

- füge ich mehr Farben hinzu, im späteren Spielverlauf hinzu, so kann es sein, dass eine Farbe eine größere Fläche einnimmt. Kommt das durch Rundungsfehler zustande?

Share this post


Link to post
Share on other sites

Bzw. wenn ich als Size Parameter 96 mitgebe, dann passt es ungefähr von der Größe her. Aber diesen Wert kann ich doch bestimmt aus der Transform Größe des Kreises bestimmen lassen oder?

Share this post


Link to post
Share on other sites
vor 15 Stunden schrieb Garzec:

- wenn ich als Parameter für die Größe ebenfalls 512 mitgebe, werden die Kanten ordentlicher / feiner. Der Kreis wird dadurch aber natürlich auch größer. Wo wird denn die Größe der Textur innerhalb von GenerateCircleTexture bestimmt?

Die Größe des Sprites wird nicht in 'GenerateCircleTexture' bestimmt. Der Parameter setzt nur die Auflösung der Textur..

Bei 'Sprite.Create()' steht der letzte Parameter für die Anzahl der Pixel, die in 1 Unit gepackt werden. Wenn zb. deine Textur 1024 Pixel breit ist, und dein Sprite 512 PixelPerUnit hat, ist dein Sprite 2 Units groß..

 

vor 16 Stunden schrieb Garzec:

füge ich mehr Farben hinzu, im späteren Spielverlauf hinzu, so kann es sein, dass eine Farbe eine größere Fläche einnimmt. Kommt das durch Rundungsfehler zustande?

Ich weiß ja nicht wie viele Farben du hinzugefügt hast, aber bei mir sehen die alle gleich groß aus..

circle.thumb.PNG.66165bfd4b21913661ca46002063da7f.PNG

Share this post


Link to post
Share on other sites

@Mr 3d

Vielleicht habe ich einen Knick in der Optik aber mir kommt die dunkelblaue Fläche minimal größer als die anderen vor.

image.png.b32651f8e3054b1ae87a6e065b75f731.png

Die Größenangabe habe ich aber noch nicht so ganz verstanden. Wenn ich für die Textur und für das Sprite 512 mitgebe, für eine höhere Auflösung, und mein Sprite Transform ein Scale von (10,10,1) hat, wie halte ich die Größe der Textur gleich zum Scale / Größe des Sprites?

 

Share this post


Link to post
Share on other sites
vor 30 Minuten schrieb Garzec:

Vielleicht habe ich einen Knick in der Optik aber mir kommt die dunkelblaue Fläche minimal größer als die anderen vor.

Tatsache.. ich würde auch sagen, dass die dunkelblaue Fläche größer ist..

Ich schätze mal das liegt daran, dass zwischen der blauen und roten Fläche genau ein Streifen an Pixeln liegt, der theoretisch zu beiden Farben 50% gehört.

Aber jeder Pixel muss sich für eine Farbe entscheiden und somit ist Blau um einen Pixel breiter..

Den Effekt kannst du minimieren, indem du eine höhere Auflösung verwendest. Dann hat der eine Pixel weniger Einfluss..

 

private Sprite generateCircleSprite(Color[] colors, int resolution)
{
 Color[] pixels = new Color[resolution * resolution];
 Vector2 center = new Vector2(resolution / 2f, resolution / 2f);
 float anglePerColor = 360f / colors.Length;
 for (int y = 0; y < resolution; y++)
 {
  for (int x = 0; x < resolution; x++)
  {
   Vector2 dir = new Vector2(x + .5f, y + .5f) - center;
   float angle = -Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg + 90;
   angle = (angle < 0 ? 360 + angle : angle);
   int colorIndex = Mathf.Min((int)(angle / anglePerColor), colors.Length - 1);
   pixels[x + y * resolution] = dir.magnitude < resolution * .5f ? colors[colorIndex] : Color.clear;
  }
 }
 Texture2D tex = new Texture2D(resolution, resolution);
 tex.SetPixels(pixels);
 tex.Apply();
 return Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(.5f, .5f), resolution);
}

Diese Methode gibt dir ein Sprite zurück, dass immer 1 Unit hoch/breit ist. Weil die Höhe/Breite in Pixeln der Textur gleich der PixelPerUnit des Sprites entspricht..

Dann kannst du die Größe deines Rades einfach mit der Transform Scale bestimmen.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×