Olen tekemässä päältä päin kuvattavaa peliä VB.NETissä. Muutama asia minua häiritsee
For Each
on melko hidas etsimään formilla olevia objektejaPictureBox
ien mennessä päällekkäin, päällimmäisin objekti peittää alimmaisen taustavärillään, vaikka asetuksena olisikin transparent
PictureBox
eja ei voi kääntää portaattomasti - ainakaan kovin nopeastiMiten pääsisin näistä em. kohdista ohi? En haluaisi käyttää erillisiä kirjastoja tai lisäosia projektini kanssa.
Ensisijaisesti kun ensimmäinen kohta on kunnossa, sitten toinen ja sitten kolmas...
Käytä formilla olevia objekteja suoraan, älä etsi niitä.
Noita mainitsemiasi picturebox-juttuja (läpinäkyvyys ja kierto) on käsitelty tällä foorumilla aikaisemminkin. Kannattaa ehkä vilkuilla aikaisempia keskusteluja aiheesta.
https://www.ohjelmointiputka.net/keskustelu/
https://www.ohjelmointiputka.net/keskustelu/
Hankala objekteja on käyttää suoraan jos niitä luodaan @ runtime
Petja kirjoitti:
Hankala objekteja on käyttää suoraan jos niitä luodaan @ runtime
VB.Netissä ihan kaikki formilla olevat objektit luodaan @ runtime. Väitteesi on epälooginen ja virheellinen.
Jos näin on; siinä tapauksessa saat alkaa kertomaan, miten toimin...
No kun luot objektin, niin laitat sen tietenkin johonkin muuttujaan. Sitten kun halut käpistellä objektia, niin käytät sitä muuttujaa. Voit laittaa ne vaikka Dictionaryyn niin voi kutsua niitä esim. nimillä tai numeroilla.
Samalla tavallahan se designerin generoima koodikin tekee.
Miten sitten foreachilla niitä haet?
Tässä esim koodi, millainen tuli kun laitoin designerilla formille yhden textboxin
Private Sub InitializeComponent() Me.TextBox1 = New System.Windows.Forms.TextBox() Me.SuspendLayout() ' 'TextBox1 ' Me.TextBox1.Location = New System.Drawing.Point(13, 13) Me.TextBox1.Name = "TextBox1" Me.TextBox1.Size = New System.Drawing.Size(100, 20) Me.TextBox1.TabIndex = 0 ' 'Form1 ' Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font Me.ClientSize = New System.Drawing.Size(284, 262) Me.Controls.Add(Me.TextBox1) Me.Name = "Form1" Me.Text = "Form1" Me.ResumeLayout(False) Me.PerformLayout() End Sub Friend WithEvents TextBox1 As System.Windows.Forms.TextBox
For Each obj As Control In Me.Controls 'Tee jotain... Next
Heh heh. No ilman foreachia sama toimii näin
'Tee jotain...
Edelleen kerroit että "etsit" formilla olevia elementtejä foreachilla. Tuo laittamasi koodinpätkä käy läpi kaikki formilla olevat kontrollit. En näe mitään etsimistä tuossa. Eli on vaikea antaa edellistä parempaa vastausta.
Jotta voisin kertoa sinulle paremman tavan tehdä joku asia, niin sinun pitäisi kertoa, mikä se asia on, jonka haluat tehdä.
Ajattele seuraava koodipätkä ilman foreachia
For Each obj As Control In Me.Controls If obj.Name.Contains("boxi") Then Me.Controls.Remove(obj) End If Next
Toimiiko?
No jos sulla olis vaikka kakikki box-nimen sisältävät objektit taulukossa, jonka nimi on Boxit, niin voisit tehdä tuon saman näin:
For Each boxi in Boxit Me.Controls.Remove(boxi) Next
tai vaikka
For i=0 to boxi.length-1 Me.Controls.Remove(boxi(i)) Next
Tosin en nyt tiedä pitääkö paikkansa väitteesi, että juuri foreach olisi se pullonkaula.
No näinhän sen toki voisi ratkaista, mutta läpi objektit on kuitenkin käytävä tavalla tai toisella. Peli ei ole läheskään valmis, joten objektien määrän kasvaessa palaamme samaan pisteeseen eli siihen hitauteen.
No montako sataa miljoonaa niitä sitten on tulossa? Väitän, että niiden löytäminen on ajallisesti pientä verrattuna niiden poistamiseen formilta.
Ei sataa miljoonaa, mutta jo 60-70 alkaa hidastamaan suorituskykyä merkittävästi. Ainakin formilla objektin liikuttaminen samaan aikaan alkaa käydä tuskallisen hitaaksi.
Luonnoksia pelistä kirjoittaessani näyttäisi siltä, että foreach jättää myös huomioimatta osan halutuista objekteista. Jos haluaisin poistaa ne, joudun suorittamaan saman foreachin 2-3 kertaa.
Tein koemielessä formin, johon generoin 2000 pictureboksia, joista puolet oli nimetty boksixxx ja puolet baksixxx.
Etsin sieltä tuolla sinun foreachilla kaikki sellaiset, joiden nimessä on boksi, ja aikaa tähän operaatioon meni 0,5 millisekuntia.
Kokeilin tehdä vielä saman 1000 kertaa ja siihen meni aikaa noin 280 millisekuntia.
Onko formilla DoubleBuffered truena? Olen itse kehittämässä avaruuspeliä (tosin hieman ehkä välillä tökkivää) jossa itse laitan vain formille tarvittavat objektit. Minulle kyllä riittää pelkkä kaksoispuskurointi ja refreshaus. Olen kylläkin törmännyt tilanteeseen, jossa näytönohjain ei kerkeä aivan kaikkeen mukaan (raketti jättää itsestään jäljen).
'formille piirto dynaamisella kontrollitaulukolla Dim ctrlArray() As Control Dim ctrlNumber As Integer Sub muutaKuvaa(numero As Integer, koko As Size, paikka As Point, kuva As Image) Dim ctrl As PictureBox = CType(ctrlArray(numero), PictureBox) With ctrl .Size = koko .Location = paikka .Image = kuva End With Me.Refresh() End Sub Sub lisääKuva(kuva As PictureBox) Me.Controls.Add(kuva) ctrlArray(ctrlNumber) = kuva ctrlNumber += 1 Me.Refresh() End Sub Sub poistaKuva(numero As Integer) Me.Controls.Remove(ctrlArray(numero)) ctrlArray(numero) = Nothing Me.Refresh() End Sub
@Grez
Se on paljon se, mutta ihmettelen kuinka suhtkoht nopean koneen kanssa ohjelma hidastuu niin paljon. Pitää tuota koodia katsella vähän lähemmin.
Miten muutin laskit tuon ajan? Tietokoneen kello ei pitäisi pystyä noin pieneen yksikköön, kun jopa 1 millisekunti (1/1000) on ongelmallinen kellosovelluksissa.
@ErroR+
Missä asetat tuon DoubleBuffered'in päälle? Kuinka minä voin tehdä sen?
Käytin System.Diagnostics.Stopwatch:ia. Ja toistin saman moneen kertaan.
Hämmentävää muuten jos käyttää tuon stopwatchin ticksejä, niin ne ei näytä olevan 0,1 mikrosekunteja (100 nanoseconds) kuten MS:n ohje väittää.
Formilla on ominaisuus DoubleBuffered. Asetat sen desingeristä tai koodista (Me.DoubleBuffered = True/False).
EDIT: Tuo ominaisuus on muuten suojattu (protected), joten sitä ei voi asettaa esim. moduulista tai toisesta luokasta.
Dictionaryllä Linq:ta käyttäen miljoona boksia löytyi 260 millisekunnissa, eli tuo foreach ei todellakaan aiheuta sen suurempaa hitautta kun mitä nyt ylipäätään tuollaisessa aivokuolleessa merkkijonoilla vertailussa on mahdollista saavuttaa.
Käyttäminen suoraan taulukosta eli hakeminen ilman merkkijonovertailuja kesti 0,004 millisekuntia eli oli noin puoli miljoona kertaa nopeampaa. (Nyt jouduin hakemaan 1000 yksikön taulukosta miljardi kertaa että sain järkevän ajan mitattua)
Eli siis hakemiseen kuluvan ajan ongelman saa käytännössä poistettua 100%:sti ihan sillä, että laittaa ne järkeviin muuttujiin. Sillä saa samalla myös poistettua tuon ongelman, ettei kaikki poistu yhdellä läpikäynnillä, kun ei poisteta samasta objektista mitä läpikäydään.
Mikset yksinkertaisesti vain pidä itse kirjanpitoa peliobjekteista ja piirrä kuvat vaikka suoraan formille DrawImage() funktiolla?
jalski kirjoitti:
Mikset yksinkertaisesti vain pidä itse kirjanpitoa peliobjekteista ja piirrä kuvat vaikka suoraan formille DrawImage() funktiolla?
Tuokin on kätevä tapa. Omassa pelissäni esim. tähdet on piirretty suoraan formille.
Varo, tuo minun esimerkki vie muistia paljon jos objekteja lisää ja poistaa monta kertaa. Taulukosta voi(ko?) poistaa objektin "Erase" -käskyllä.
Omissa luonnoksissani olen asettanut Timerin liikuttamaan ukkeleita foreachin avulla. En ole tutustunut VB.NET grafiikkajuttuihin kovin tarkasti, mutta pystyykö DrawImage() funktion piirtämiä ukkeleita sitten ohjaamaan samalla tekniikalla Timeriä hyödyntäen?
Petja kirjoitti:
pystyykö DrawImage() funktion piirtämiä ukkeleita sitten ohjaamaan samalla tekniikalla Timeriä hyödyntäen?
Miksei pystyisi? Pistä tuohon ajastimen kutsumaan funktioon kutsut peliobjektien päivitys -ja piirtofunktioihin, niin homman pitäisi toimia hienosti.
ErroR++ kirjoitti:
Onko formilla DoubleBuffered truena? Oken itse kehittämässä avaruuspeliä (tosin hieman ehkä välillä tökkivää) jossa itse laitan vain formille tarvittavat objektit. Minulle kyllä riittää pelkkä kaksoispuskurointi ja refreshaus. Olen kylläkin törmännyt tilanteeseen, jossa näytönohjain ei kerkeä aivan kaikkeen mukaan (raketti jättää itsestään jäljen).
Otanpas nyt hiukan kantaa tähänkin... Itse en kovin hyvin tunne .NET frameworkkiä, mitä nyt pari tuntia olen käyttänyt tutustumiseen opiskellessani Component Pascalia. Kirjoittelen kyseisellä yhdistelmällä todennäköisesti kokeeksi jonkunlaisen pelin tässä lähipäivinä, kunhan oikeilta töiltäni ehdin.
.NET gurut saavat korjata, mutta olisiko kuitenkin parempi hoitaa tuplapuskurointi itse manuaalisesti piirtämällä kaikki ensin offscreen bitmappiin?
Eli käytänössä siis pitäisi pitää huolta, että kontrolli mihin piirretään ei turhaan yrittäisi piirtää itse taustaansa. Kyseisen kontrollin Paint eventtiin laitetaan koodi, mikä piirtää kaiken ensin tuohon offscreen bitmappiin ja sen jälkeen tuon offscreen bitmapin sisällön kontrolliin. Ajastimen event funktiossa suoritetaan peliobjektien päivityskoodi ja lopuksi kutsutaan Invalidate() funktiota.
Jotenkin näin meinasin...
Paketissa siis yksinkertainen peliobjektien liikuttelu kokeilu Component Pascalilla kirjoitettuna.
No tuolla DoubleBuffered -arvolla säästää aikaa, kunhan muistaa Refreshata formin heti muutosten jälkeen.
Pieni lisävaiva toteutusvaiheessa korvautuu onneksi yleensä kyllä saavutetulla joustavuudella ja lisääntyneellä kontrollilla tapahtumiin.
Tein tuohon ensimmäiseen Component Pascal .NET piirto testiini pari muutosta ja mielestäni toteutus on kyllä kohtuullisen helppo ja suoraviivainen.
MODULE Smileys2; IMPORT Sys := "[mscorlib]System", Cpm := "[System]System.ComponentModel", Wfm := "[System.Windows.Forms]System.Windows.Forms", Drw := "[System.Drawing]System.Drawing", WinMain; CONST frmWidth = 360; frmHeight = 240; timerInterval = 20; TYPE MainForm = POINTER TO RECORD (Wfm.Form) components: Cpm.Container; timer: Wfm.Timer END; Smiley = POINTER TO RECORD img: Drw.Image; x, y: INTEGER; vx, vy: INTEGER END; VAR frm: MainForm; img: Drw.Image; buffer: Drw.Bitmap; smileys: POINTER TO ARRAY OF Smiley; (* ==================================================================== *) PROCEDURE (smiley: Smiley) Init(x, y, vx, vy: INTEGER; img: Drw.Image), NEW; BEGIN smiley.x := x; smiley.y := y; smiley.vx := vx; smiley.vy := vy; smiley.img := img END Init; (* ==================================================================== *) PROCEDURE (smiley: Smiley) Draw(frm: MainForm; g: Drw.Graphics), NEW; BEGIN g.DrawImage(smiley.img, Drw.Point.init(smiley.x, smiley.y)) END Draw; (* ==================================================================== *) PROCEDURE (smiley: Smiley) Update(width, height: INTEGER), NEW; BEGIN INC(smiley.x, smiley.vx); IF ((smiley.x + smiley.img.get_Width()) >= width) THEN smiley.vx := -smiley.vx; smiley.x := width - smiley.img.get_Width() END; IF (smiley.x <= 0) THEN smiley.vx := -smiley.vx; smiley.x := 0 END; INC(smiley.y, smiley.vy); IF ((smiley.y + smiley.img.get_Height()) >= height) THEN smiley.vy := -smiley.vy; smiley.y := height - smiley.img.get_Height() END; IF (smiley.y <= 0) THEN smiley.vy := -smiley.vy; smiley.y := 0 END END Update; (* ==================================================================== *) PROCEDURE (frm: MainForm) Dispose*(disposing: BOOLEAN); BEGIN IF disposing THEN IF frm.components # NIL THEN frm.components.Dispose() END; frm.Dispose^(disposing) END END Dispose; (* ==================================================================== *) PROCEDURE (frm: MainForm) OnTimerTick(sender: Sys.Object; e: Sys.EventArgs), NEW; VAR i: INTEGER; BEGIN FOR i := 0 TO LEN(smileys) - 1 DO smileys[i].Update(frm.get_ClientSize().get_Width(), frm.get_ClientSize().get_Height()) END; frm.Invalidate() END OnTimerTick; (* ==================================================================== *) PROCEDURE (frm: MainForm) OnPaintBackground*(e: Wfm.PaintEventArgs); BEGIN END OnPaintBackground; (* ==================================================================== *) PROCEDURE (frm: MainForm) OnPaint*(e: Wfm.PaintEventArgs); VAR i: INTEGER; g: Drw.Graphics; BEGIN IF buffer = NIL THEN buffer := Drw.Bitmap.init(frm.get_ClientSize().get_Width(), frm.get_ClientSize().get_Height()) END; g := Drw.Graphics.FromImage(buffer); g.Clear(Drw.Color.get_White()); FOR i := 0 TO LEN(smileys) - 1 DO smileys[i].Draw(frm, g) END; g.Dispose(); e.get_Graphics().DrawImageUnscaled(buffer, 0, 0) END OnPaint; (* ==================================================================== *) PROCEDURE (frm: MainForm) OnSizeChanged*(e: Sys.EventArgs); BEGIN IF buffer # NIL THEN buffer.Dispose(); buffer := NIL END; IF frm.get_Width() < frmWidth THEN frm.set_Width(frmWidth) END; IF frm.get_Height() < frmHeight THEN frm.set_Height(frmHeight) END; frm.OnSizeChanged^(e) END OnSizeChanged; (* ==================================================================== *) PROCEDURE (frm: MainForm) InitializeComponent(), NEW; BEGIN NEW(frm.components); NEW(frm.timer); frm.SuspendLayout(); frm.set_StartPosition(Wfm.FormStartPosition.CenterScreen); frm.set_Text("Happy Faces"); frm.set_AutoScaleBaseSize(Drw.Size.init(5, 13)); frm.set_ClientSize(Drw.Size.init(frmWidth, frmHeight)); REGISTER(frm.timer.Tick, frm.OnTimerTick); frm.ResumeLayout(FALSE) END InitializeComponent; (* ==================================================================== *) BEGIN img := Drw.Image.FromFile("smiley.png"); NEW(frm); frm.InitializeComponent(); NEW(smileys, 6); NEW(smileys[0]); smileys[0].Init(100, 100, 2, 1, img); NEW(smileys[1]); smileys[1].Init(50, 50, -2, 2, img); NEW(smileys[2]); smileys[2].Init(220, 100, -1, 2, img); NEW(smileys[3]); smileys[3].Init(50, 200, 2, -1, img); NEW(smileys[4]); smileys[4].Init(80, 150, 2, 1, img); NEW(smileys[5]); smileys[5].Init(150, 150, -1, -1, img); frm.timer.set_Interval(timerInterval); frm.timer.Start(); Wfm.Application.Run(frm) END Smileys2.
Aihe on jo aika vanha, joten et voi enää vastata siihen.