Pagina 1 di 2

Problema thread/timer per fare animazione

Inviato: 12 giu 2017, 14:32
da Zeno
Premetto che non sto usando Unity o altri engine, ma solo e solamente visual studio community 2017.

Devo fare in modo che ogni 60 secondi si attivi una animazione fatta in modo "manuale" con una pictureBox che cambia il suo backgroundImage ogni secondo per emulare il framerate.
Ho creato il codice per farlo. L'animazione la fa, però quando cerco di interrompere i due thread necessari l'animazione fatta in questo modo cambiando la flag e chiamo la funzione per cambiare nuovamente il backgroundImage, di tanto in tanto (non sempre) mi da un messaggio di errore dove dice "L'oggetto è correntemente utilizzato altrove".
Non riesco a venirne a capo. Il mio metodo per animare in c# è un casino di suo...

Esiste una soluzione un po' più semplice?
Questo è il codice che sto usando. Ho commentato in giro le parti più importanti

Codice: Seleziona tutto

//DICHIARAZIONE DEL THREAD PRINCIPALE
   System.Threading.Thread thread;
        System.Threading.ThreadStart start;
        bool exitFlag = false, exitFlagAnim = false;

public Form1()
        {
            InitializeComponent();
           
            //THREAD CREATION
            start = new System.Threading.ThreadStart(eventTimer); //eventTimer è il timer che fa scattare l'animazione dopo 60 secondi
            System.Threading.Thread thread = new System.Threading.Thread(start);
            thread.Start();
        }
       
        //USO QUESTO PER RIDIMENSIONARE LE IMMAGINI IN BASE ALLA DIMENSIONE DEL CONTENITORE
        private static Image ResizeImage(Image image, Size newSize)
        {
            Image newImage = new Bitmap(newSize.Width, newSize.Height);

            using(Graphics GFX = Graphics.FromImage((Bitmap)newImage))
            {
                GFX.DrawImage(image, new Rectangle(Point.Empty, newSize));
            }
            return newImage;
        }
       
       
         //TIMER EVENT
        private void eventTimer()
        {
            myTimer = new Timer();
            myTimer.Tick += new EventHandler(TimerEventProcessor); //PASSATI I 60 SECONDI CHIAMA L'ALTRA FUNZIONE

            // Sets the timer interval to 60 seconds.
            myTimer.Interval = 60000;
            myTimer.Start();

            // Runs the timer, and raises the event.
            while (exitFlag == false) // UNA VOLTA IMPOSTATO EXITFLAG = TRUE IL THREAD TERMINA IN MODO PULITO //RIMANE IN LOOP
            {
                // Processes all the events in the queue.
                Application.DoEvents();
            }
        }

        // This is the method to run when the timer is raised.
        private void TimerEventProcessor(Object myObject, EventArgs myEventArgs) //SERVE SOLO PER CREARE IL THREAD PER ANIMARE
        {
            myTimer.Stop();

            //THREAD DEDICATO ALL'ANIMAZIONE VERA E PROPRIA
            System.Threading.ThreadStart startAnim = new System.Threading.ThreadStart(startbackgroundAnim);
            System.Threading.Thread threadAnim = new System.Threading.Thread(startAnim);
            threadAnim.Start();
        }

        //TIMER EVENT BACKGROUND
        private void startbackgroundAnim()//STESSA STRUTTURA DEL PRIMO EVENT TIMER
        {
            animTimer = new Timer();
            animTimer.Tick += new EventHandler(backgroundAnim);
           
            animTimer.Interval = 1000; //PER EMULARE IL FRAMERATE DI 1 SECONDO TRA UNA IMMAGINE E L'ALTRA
            animTimer.Start();
           
            while (exitFlagAnim == false)
            {
                Application.DoEvents();
            }
        }
        private void backgroundAnim(Object myObject, EventArgs myEventArgs)
        {
            if (animc < 3 && ingame == false)
            {
                animTimer.Stop();
      
      
      //BACKGROUND E' LA PICTUREBOX CHE CONTIENE L'ANIMAZIONE
                background.BackgroundImage = ResizeImage(animArray[animc], ClientSize);
                BackgroundImage = ResizeImage(animArray[animc], ClientSize);

                animc++;
                repeatAnim++;

                if(repeatAnim < 18 && animc == 3) //PERMETTE DI RIPETERE L'ANIMAZIONE DI 3 FRAME
                                            //REPEATANIM DEVE ESSERE MULTIPLO DI 3 DATO CHE I FRAME SONO 3
                {
                    animc = 0;// IN QUESTO MODO "animc < 3" NON SARA' FALSA FINCHE' NON AVRA' CAMBIATO 18 VOLTE L'IMMAGINE
                }

                //FA RIPARTIRE L'ANIMAZIONE
                animTimer.Enabled = true;
            }
            else
            {
                //REIMPOSTA DA CAPO DATO CHE NON HO PIU' BISOGNO DELL'ANIMAZIONE
                animTimer.Stop();

                exitFlagAnim = true; //FERMA IL TIMER
                exitFlag = true;

                repeatAnim = 0;
                animc = 0;

                myTimer.Enabled = true;

                background.BackgroundImage = ResizeImage(Array[c], ClientSize);
                BackgroundImage = ResizeImage(Array[c], ClientSize);
            }
        }
       
       
       
        //E' LA PICTUREBOX/PULSANTE CHE INTERROMPE L'ANIMAZIONE E FA RIPARTIRE IL CODICE
        private void pictureBox1_Click(object sender, EventArgs e)
        {
            ingame = true;//E' LA FLAG CHE FA INIZIARE IL PROCESSO DI CHIUSURA DEI THREAD. GUARDA SOPRA IN "private void backgroundAnim"


            pictureBox1.Visible = false;
           
            forward.Visible = true;

            c++;
            background.BackgroundImage = ResizeImage(Array[1], this.ClientSize);
            BackgroundImage = ResizeImage(Array[1], this.ClientSize);
        }

Re: Problema thread/timer per fare animazione

Inviato: 14 giu 2017, 15:37
da NicolaLC
Ciao, ho letto il codice ma senza il progetto intero non riesco a verificare la mia teoria.

I thread sono molto complessi soprattutto per via di gestione e rilascio delle risorse.

Linguaggi come C#, C++ e altri che permettono la gestione manuale delle risorse richiedono una logica precisa affinchè il programma ne gestisca la concorrenza.

Secondo me la natura del tuo problema è data dal fatto che più thread cerchino di accedere alla stessa risorsa e/o dei thread restano appesi e quanto scatta l'eventTimer cercano di accedere ad una risorsa che non esiste più e/o viene utilizzata da nuovi threads.

Un piccolo esempio:

private void eventTimer()
{
myTimer = new Timer();
myTimer.Tick += new EventHandler(TimerEventProcessor); //PASSATI I 60 SECONDI CHIAMA L'ALTRA FUNZIONE

// Sets the timer interval to 60 seconds.
myTimer.Interval = 60000;
myTimer.Start();

// Runs the timer, and raises the event.
while (exitFlag == false) // UNA VOLTA IMPOSTATO EXITFLAG = TRUE IL THREAD TERMINA IN MODO PULITO //RIMANE IN LOOP
{
// Processes all the events in the queue.
Application.DoEvents();
}

myTimer.Stop(); // una volta richiesto l'exit è necessario fermare il timer in quanto potrebbe cercare di accedere a risorse non più disponibili
}

Ad esempio in questa funzione va tutto bene, però ritengo che sia necessario fermare il thread una volta che l'exitFlag viene impostato a true semplicemente per essere sicuro che il thread non proceda nella sua operatività.

Se questo non viene effettuato, e per caso il thread continua, potrebbe cercare di accedere ad una risorsa usata da una nuova istanza dello stesso thread, o da qualche nuovo thread, generando un deadlock sulla risorsa.

Ribadisco che tutto quello che ho scritto è una supposizione leggendo il tuo codice, non potendolo provare non posso confermarti che sia una soluzione pratica al problema, ma potrebbe aiutarti nel capire la natura dello stesso.

Ciao!:)

Re: Problema thread/timer per fare animazione

Inviato: 14 giu 2017, 20:38
da Zeno
Si infatti è come dici tu perché ho ristudiato meglio il codice e ora va liscio, però visto che il mio game designer è uno che non si fa bastare un "altre animazioni potrebbero creare dei bug", vorrei cercare perlomeno di rendere questa soluzione praticabile più volte.

Ho usato i thread per paura del fatto che i timer potessero bloccare il flusso di codice cioè far impallare il programma, quindi mi viene da chiedere... sono davvero necessari?
'sto pezzo di codice

while (exitFlag == false) // UNA VOLTA IMPOSTATO EXITFLAG = TRUE IL THREAD TERMINA IN MODO PULITO //RIMANE IN LOOP
{
// Processes all the events in the queue.
Application.DoEvents();
}

dovrebbe permettere di eseguire le operazioni in coda, quindi ciò implica che il codice si può snellire ulteriormente togliendo i thread lasciando solo i timer?
Mi faresti un grande favore se mi rispondessi, grazie!

Re: Problema thread/timer per fare animazione

Inviato: 15 giu 2017, 11:52
da NicolaLC
Ciao,

sono contento di esserti stato d'aiuto.

Ho trovato un framework che potrebbe aiutarti:https://code.google.com/archive/p/dot-net-transitions/.

Ad ogni modo, se desideri gestire tutto in modo custom, dovresti seguire la logica applicata dagli engine, ovvero:

[Loop 1 frame]
[Inizio frame]
[Gestione FixedUpdate]
- gestione della fisica degli oggetti (e.g transizioni da un punto a ad un punto b)
- qui dovresti gestire lo spostamento di un oggetto ad esempio
[Gestione Update]
- gestione della logica (ad esempio input dell'utente) o altre animazioni che non riguardano un movimento fisico di un elemento (e.g. opacity, ecc..)
[Fine Frame]
[End loop]

Facciamo un piccolo esempio pratico:

static void Main(){
Application.Run(new Form1()); // mostra la grafica
}

private void Form1_Load(object sender, System.EventArgs e) {
// gestisci load della form
}

private System.Windows.Forms.Timer timer1; // questo è un timer chiamato ogni frame, è il nostro loop


private void Rotate( Graphics graphics, LinearGradientBrush brush )
{
// qui viene eseguita un animazione
}

private void Rotate(Graphics graphics)
{
angle += 5 % 360;
Rotate(graphics, GetBrush());
}

private void timer1_Tick(object sender, System.EventArgs e)
{
// questo è il CORE delle animazioni, viene chiamato ogni frame e qui puoi gestire le animazioni
Rotate(CreateGraphics());
}

private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Rotate(e.Graphics); // questa funzione viene chiamata ogni volta che la form effettua un paint, quindi il sistema ricarica la componente grafica
}

In questo esempio abbiamo un timer (timer1) che viene chiamato ad ogni frame ed è relativo al sistema operativo (quindi al singolo clock della scheda madre in relazione alle impostazioni del SO), nella funzione timer1_Tick vado a gestire un animazione (ma possono essere N) in modo che tale animazione venga richiamata ad ogni frame.

Facciamo finta che tu abbia N animazioni, il risultato dovrebbe essere il seguente:

private void timer1_Tick(object sender, System.EventArgs e)
{
// questo è il CORE delle animazioni, viene chiamato ogni frame e qui puoi gestire le animazioni
DoAnimations();
}

private void DoAnimations()
{
if(needToMoveAnimation) MoveAnimation();
if(needToRotateAnimation) RotateAnimation();
if(otherAnimationsToPerform) PerformGenericAnimation();
}

Come puoi vedere ad ogni frame viene chiamata la funzione DoAnimations che si occupa di chiamare le funzioni di animazione se necessario (tutto quello che ho scritto è ovviamente un esempio).

Prendiamo ad esempio il caso in cui l'utente possa muovere un cubo con le freccie WASD:

private void timer1_Tick(object sender, System.EventArgs e)
{
// questo è il CORE delle animazioni, viene chiamato ogni frame e qui puoi gestire le animazioni
DoAnimations();
}

private void DoAnimations()
{
MoveAnimation();
}

private void MoveAnimation()
{
float _hMov = Input.GetAxisRaw("Horizontal"); // -1 if A is pressed, 1 if D is pressed, 0 else
float _vMov = Input.GetAxisRaw("Vertical"); // -1 if S is pressed, 1 if W is pressed, 0 else
if(_hMov) // A or D pressed (Unity c# library)
myTargetGraphics.pos.x += _hMov; // move target on x axis

if(_vMov) // A or D pressed (Unity c# library)
myTargetGraphics.pos.y += _hMov; // move target on y axis
}

Ovviamente questo codice è mezzo pseudo codice e mezzo scritto in Unity C# in quanto sono abituato ad usare Unity e mi viene piu semplice.

Spero di esser stato chiaro, in caso scrivimi pure!

Ciao!:)

Re: Problema thread/timer per fare animazione

Inviato: 15 giu 2017, 12:09
da Zeno
Wow... non avevo mai preso in considerazione di usare librerie esterne.
Cercherò di studiare bene la libreria transitions così posso togliere questa porcheria di codice se possibile, grazie!

Re: Problema thread/timer per fare animazione

Inviato: 15 giu 2017, 12:16
da NicolaLC
Figurati è un piacere, te lo consiglio anche perchè in genere sono super testate e affidabili, quindi non ti devi più preoccupare per il tuo Game designer :D

Re: Problema thread/timer per fare animazione

Inviato: 15 giu 2017, 16:19
da Tiziano Lena
Guardando le API di altre librerie 'a basso livello' (come potrebbero essere COCOS 2D o SDL o SFML) avrai modo poi di vedere quale è la struttura tipica di un gioco e come meglio proseguire, se vorrai, l'approccio di codice scritto da te nativamente.

Re: Problema thread/timer per fare animazione

Inviato: 16 giu 2017, 10:01
da Zeno
già che ci sto ne approfitto per fare un'altra domanda:

Se io metto una picturebox sopra un'altra picturebox più grande (stesse dimensioni del form) e imposto il background della prima trasparente, non diventa realmente trasparente in modo che si veda "l'immagine della picturebox di sotto" perché prende invece l'immagine del background del form.

Per esempio se io imposto una immagine alla picturebox più grande, metto la picturebox più piccola sopra quest'ultima e imposto la trasparenza mentre il background del form è grigio, come risultato il background della picturebox più piccola diventa grigio, cioé un rettangolo grigio sopra una immagine!

Esiste un modo per impostare la trasparenza in modo "normale"? Cioè legare la trasparenza con l'immagine della picturebox grande?

La soluzione "rattoppata" che sto adottando è quella di impostare il background del form uguale a quella della picturebox grande. Però quando si tratta di animare, dà un effetto glitch tipo che quel rettangolo di picturebox piccolo non cambia background in modo uniforme.

Re: Problema thread/timer per fare animazione

Inviato: 16 giu 2017, 10:07
da NicolaLC
Ciao, non impostare la trasparenza, usa invece quanto segue:

myPicBox.BackColor = Color.Transparent;

Ciao!

Re: Problema thread/timer per fare animazione

Inviato: 16 giu 2017, 10:14
da Zeno
Ho impostato così

InitializeComponent();
pictureBox1.BackColor = Color.Transparent;

ma non cambia nulla :c

EDIT:
Ok ho trovato la soluzione da solo. Bisogna impostare la parentela con la picturebox grande nel mio caso in questo modo:

InitializeComponent();
pictureBox1.Parent = background;

Si nota ancora un effetto glitch ogni tanto, però almeno non si manifesta a ogni cambio di frame :D spero che questo possa essere utile a qualcuno in futuro.