Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: Pelin toteuttaminen (VB.NET)

Sivun loppuun

Petja [02.12.2011 20:10:59]

#

Olen tekemässä päältä päin kuvattavaa peliä VB.NETissä. Muutama asia minua häiritsee

Miten 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...

Grez [02.12.2011 20:13:53]

#

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/22710-kuvan-kääntö-portaattomasti-90-astetta-net
https://www.ohjelmointiputka.net/keskustelu/19891-vb-net-näytön-nopeuden-optimointia

Petja [02.12.2011 20:15:13]

#

Hankala objekteja on käyttää suoraan jos niitä luodaan @ runtime

Grez [02.12.2011 20:19:19]

#

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.

Petja [02.12.2011 20:20:53]

#

Jos näin on; siinä tapauksessa saat alkaa kertomaan, miten toimin...

Grez [02.12.2011 20:23:17]

#

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

Petja [02.12.2011 20:27:00]

#

For Each obj As Control In Me.Controls
'Tee jotain...
Next

Grez [02.12.2011 20:27:47]

#

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ä.

Petja [02.12.2011 20:32:10]

#

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?

Grez [02.12.2011 20:39:40]

#

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.

Petja [02.12.2011 20:42:41]

#

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.

Grez [02.12.2011 20:45:53]

#

No montako sataa miljoonaa niitä sitten on tulossa? Väitän, että niiden löytäminen on ajallisesti pientä verrattuna niiden poistamiseen formilta.

Petja [02.12.2011 20:47:51]

#

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.

Grez [02.12.2011 20:53:19]

#

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.

ErroR++ [02.12.2011 20:57:04]

#

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

Petja [02.12.2011 20:57:05]

#

@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?

Grez [02.12.2011 20:59:00]

#

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ää.

ErroR++ [02.12.2011 21:01:23]

#

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.

Grez [02.12.2011 21:02:36]

#

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.

jalski [02.12.2011 21:07:05]

#

Mikset yksinkertaisesti vain pidä itse kirjanpitoa peliobjekteista ja piirrä kuvat vaikka suoraan formille DrawImage() funktiolla?

ErroR++ [02.12.2011 21:13:56]

#

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ä.

Petja [02.12.2011 23:19:11]

#

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?

jalski [03.12.2011 18:06:03]

#

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.

jalski [05.12.2011 22:08:25]

#

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.

jalski [07.12.2011 00:15:02]

#

Jotenkin näin meinasin...

Paketissa siis yksinkertainen peliobjektien liikuttelu kokeilu Component Pascalilla kirjoitettuna.

ErroR++ [08.12.2011 19:52:52]

#

No tuolla DoubleBuffered -arvolla säästää aikaa, kunhan muistaa Refreshata formin heti muutosten jälkeen.

jalski [08.12.2011 20:39:20]

#

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.

Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta