Ergebnis 1 bis 9 von 9

Thema: [VX Ace] Schadens-Popups und Performancefragen

  1. #1

    [VX Ace] Schadens-Popups und Performancefragen

    Hi nochmals!
    Vorneweg, ich bin immernoch ziemlich unerfahren was Ruby angeht und beiße mich da langsam durch Arbeite mit dem RPG Maker VX Ace.

    Ist es möglich einfach Zahlen auf den Bildschirm zu zeichnen, ohne diese in ein Window zu zeichnen? (Hab in der Bitmap-Klasse keine entsprechende Methode finden können)
    Habs dann so gelöst, dass ich ein Objekt der Window_Base-Klasse erstelle und davon dann die Hintergrundgrafiken lösche.
    Gibt es eine Möglichkeit die Höhe und Breite eines Fensters schon bei der Erstellung dynamisch an den folgenden Inhalt anzupassen? Ist dies überhaupt wichtig für die Performance, d.h. spielt es eine große Rolle, ob ich z.B. ein 300x300 Fenster, oder ein 60x30 Fenster bewege? (Hintergrundgrafik wird meines Wissens nach in meinem Skript nicht gezeichnet)

    Habe nun das Problem wenn ich in einem CommonEvent den Skriptschnipsel "dmgpop(160,120,12)" gefolgt von "dmgpop(100,120,24)" aufrufe, wird zuerst der eine komplett ausgeführt, dann der andere. Ich hätte aber gerne, dass beide gleichzeitig ablaufen!
    Gibts hier Probleme mit den lokalen Variablen? (@) Hab mir überlegt eine globale Variable, quasi als Seriennummer, $dmgpopup zu benutzen, damit ich verschiedene Objekte erstellen kann?! (bin mir hier nicht sicher.) Also quasi anstatt @popup eine Art @popup($dmgpopup) wobei ich hier die richtige Syntax nicht kenne.

    Würde mich freuen, wenn sich jemand den Code anschauen und mir bei den Problemen helfen könnte. Wenn jemand eine bessere Lösungsidee hat, würde ich mich noch mehr drüber freuen, da ich mir zml sicher bin, dass meine Umsetzung hier stark hinkt. (Auch gerne, was die anschließende Bewegung des PopUps betrifft )

    Code:
    class Game_Interpreter
      
      def dmgpop(x,y, number)
          # Erzeugt das Bild
        height = 300
        width  = 300
        @popup = Window_Base.new(x,y,height,width)
        @popup.setup_message_font
         
        @empty = Cache.system("Window - No Frame.png")
        @popup.windowskin = @empty
        @popup.back_opacity = 0
        
        @popup.draw_text_ex(0,0,number.to_s)
         
          # Bewegt das Bild
        addx = 0
        addy = 0
          
        while(addy > -40)
          addy -= 10
          addx += 2
          @popup.move((x+addx),(y+addy),height,width)
          wait (2)
        end
        while(addy < 40)
          addy += 7
          addx += 5
          @popup.move((x+addx),(y+addy),height,width)
          wait (2)
        end
        wait(20)
        @popup.dispose
    
      end
      
      
    end

  2. #2
    Für soetwas würde ich dir zuraten eine eigene separate Sprite-Klasse zu schreiben mit welcher du beliebigen Text auf dem Bildschirm darstellen kannst.

    Eine Klasse könnte aussehen wie folgt: (ich habe schon seit langem nichtmehr mit Ruby gearbeitet daher könnte der Code nicht ganz korrekt sein.)
    Code:
    class Sprite_Damage_Pop_Up
      
      def initialize(viewport = nil)
        @sprite = Sprite.new(viewport)
      end
      
      def set(x,y,text)
        bitmap = Bitmap.new(text.size * 32, 32)
        @sprite.bitmap = bitmap
        bitmap.draw_text(0,0,bitmap.width,bitmap.height,text,1)
        @sprite.x = x
        @sprite.y = y
      end
      
      def dispose
        @sprite.bitmap.dispose
        @sprite.dispose
      end
      
    end
    Ein Objekt dieser Klasse kannst du dann so oft instanziieren wie du möchtest und über "set(x,y,text)" die Position und den Text des Popups sofort setzen.
    Die Größe des Bitmaps auf welchem gezeichnet wird wäre:
    Breite = text.size * 32
    Höhe = 32
    Einfach nur als Standardwerte gewählt von mir. Das kann man auch eleganter lösen, es dient nur zu Anschauungszwecken.

    Du kannst dann natürlich alle möglichen Arten von Hilfsmethoden in der Klasse definieren wie du sie brauchen solltest.

  3. #3
    Danke Cornix! Deine Lösung ist wesentlich eleganter, als das was ich mir da zusammengeschustert hatte Auf die Idee einfach eine neue Sprite-Klasse zu schreiben wäre ich wohl nie gekommen :x Da muss ich noch flexibler werden.
    Der Einbau hat so auch gleich direkt funktioniert, nur wenn ich dann eine move-Methode machen möchte funktioniert die nicht, weil ich einen "disposed sprite"-Error bekomme :/
    Ich verstehe aber nicht warum, da wenn ich den Sprite einfach nur zeichne er gut ne Sekunde oder zwei zu sehen ist, bevor er automatisch gelöscht wird. Insgesamt würde mich interessieren, nach wie viel Frames die "dispose"-Methode aufgerufen wird, oder woran das festgemacht ist?!
    Selbst wenn ich die move-Methode direkt nach der Erstellung aufrufe ist der Sprite schon "disposed" Kann mir hier wer helfen? (Mein KS ist im Moment eine heftige Mischung aus Scriptschnipseln und Eventbefehlen. Müsste ich eine neue Scene für mein KS anlegen, damit Sprites nicht gleich disposed werden? Oder hat das keinen Zusammenhang?)

    Hier noch der aktuelle Code:
    Code:
    class Sprite_Damage_Pop_Up < Sprite_Base
      
      def initialize(viewport = nil)
        @sprite = Sprite.new(viewport)
        @text = nil
      end
      
      def update
        super
      end
      
      def set(x,y,text)
        @text = text
        bitmap = Bitmap.new(text.size * 32, 32)
        @sprite.bitmap = bitmap
        bitmap.draw_text(0,0,bitmap.width,bitmap.height,text,1)
        @sprite.x = x
        @sprite.y = y
        self.moveup
      end
      
      def dispose
        super
        @sprite.bitmap.dispose
        @sprite.dispose
      end
      
      def moveup
        p("test")
        self.set(self.x+56,self.y,@text)
        
      end
      
    
      
    end

  4. #4
    Die dispose-Methode musst du selber ausführen wenn du den Sprite entfernen lassen willst.

    Was ich mir vorstellen kann was passiert ist jedoch, dass du vielleicht den Sprite niemals in einer Variable speicherst und er deshalb von der Garbage-Collection aufgeschnapt und gelöscht wird.
    Zeig uns vielleicht einmal deinen Code mit welchem du den Sprite erstellst.

  5. #5
    Der ist ziemlich simpel.
    Code:
    @> Script: @test = Sprite_Damage_Pop_Up.new
    @> Script: @test.set(0,50,"12")
    Aufgerufen wird das ganze aus einem parallelen Prozess welches einfach nur den Gamepad-Button-Input abfragt. Hab grad eben mal ne globale Variable draus gemacht, hat aber auch nicht geholfen. (Wenn ich hier so ein @-Objekt, also ein lokales erzeuge, ist das dann ein lokales Objekt von meinem Game-Objekt, also bleibt sowieso solange vorhanden, wie mein Spiel läuft?)

  6. #6
    Zitat Zitat
    (Wenn ich hier so ein @-Objekt, also ein lokales erzeuge, ist das dann ein lokales Objekt von meinem Game-Objekt, also bleibt sowieso solange vorhanden, wie mein Spiel läuft?)
    @ Variablen sind Instanzvariablen (oder Attribute, Member-Variablen und wie man die noch so nennt). Wenn du die in einem parallelen Event anlegst, sind sie sie an den Event-Interpreter gebunden, der wiederum an das jeweilige Event gebunden ist. Von daher hast du recht: Solange du die Map nicht wechselst, bleibt die Variable am "Leben".
    Wenn du den Code hingegen mehrfach ausführst, wird die Variable neu zugewiesen und der alte Sprite wird vom GarbageCollector gelöscht. Darum verschwindet die Grafik nach wenigen Sekunden.



    Aus dem Grund macht es Sinn immer zwei Klassen zu schreiben: Eine Game-Klasse und eine Sprite-Klasse:
    Code:
    class Game_DamageDisplay
    
      MOVE_UP = 40 # Anzahl Pixel, die sich die Anzeige bewegen soll
    
      def initialize x, y, damage, time
        @x = x
        @y = y
        @damage = damage
        @time = time
        @target= Graphics.frame_count + time
      end
    
      # gibt einen prozentualen Wert von 0.0 bis 1.0 an, wie weit die Anzeige fortgeschritten ist
      def progress
        duration= @target - Graphics.frame_count
        [(@time - duration).to_f / @time, 1.0].min
      end
    
      def y
        @y - progress*MOVE_UP
      end
    
      def opacity
        255 * (1.0-progress)
      end
    
      def finished?
        progress >= 0.99 # kann jetzt gelöscht werden 
      end
    
    end
    
    class Game_Screen
      def damage_displays
        displays = (@damage_displays ||= [])
        displays.delete_if {|display| display.finished?}
        displays
      end
    end
    
    class Game_Screen
      alias update_damage_displays update
      def update
        update_damage_displays
        @sprite_damage_displays ||= []
        displays = $game_screen.damage_displays
        # lösche alle Displays die nicht mehr benötigt werden
        @sprite_damage_displays[displays.size..-1].each {|display| display.dispose}
        @sprite_damage_displays = @sprite_damage_displays[0...displays.size]
        # update den Rest
        displays.each_with_index {|display, index| 
          @sprite_damage_displays[index] ||= Sprite_DamageDisplay.new
          @sprite_damage_displays[index].refresh(display)
        }
      end
    
      alias dispose_damage_displays dispose
      def dispose
        dispose_damage_displays
        @sprite_damage_displays.each {|d| d.dispose}
        @sprite_damage_displays.clear
      end
    
    end
    So könnte das z.B. aussehen. Dann fügst du jede neue Schadensanzeige in den Array $game_screen.damage_displays ein und in Spriteset_Map wird dafür gesorgt, dass für jede Anzeige ein entsprechender Sprite angelegt und angezeigt wird.


    Edit: Ich seh grade, du arbeitest mit dem VX Ace. Mein Code war eher für den XP gedacht. Aber das kriegst du ja leicht angepasst. Noch eine Anmerkung zu deinem Fehler: Das Problem ist, dass deine Klasse von Sprite erbt und Sprite als Instanzvariable hat. Beides macht keinen Sinn. Entweder du erstellst eine Klasse die von Sprite erbt, ODER eine Klasse die eine Instanzvariable vom Typ Sprite hat. Im ersteren Fall musst du aufpassen, dass du die initialize Methode von Sprite aufrufst (konkret: dass du in deiner initialize Methode super() aufrufst). Andernfalls wird der Sprite nicht angelegt. Aus dem Grund kommt bei dir immer die Dispose-Fehlermeldung: Solange kein intialize aufgerufen wurde, ist der Sprite noch nicht angelegt und daher "disposed".

    Geändert von -KD- (17.11.2012 um 16:35 Uhr)

  7. #7
    Das entpuppt sich ja als immer komplexer hier Dachte eigentlich, dass eine Zahl zeichnen und bewegen kein großer Akt ist, so kann man sich täuschen :P

    Noch kurz eine allgemeine Frage: In GubiDs Video Tutorials benutzt er immer eine "Find in All Sections"-Suchfunktion, die ich äußerst praktisch finde, wie kann ich die denn öffnen?

    @-KD-
    Danke für den Code, das ist schonmal super! Jetzt muss ich mich nur noch durch den ganzen Brocken durchknabbern Die Game_DamageDisplay - Klasse von dir verstehe ich schon, vorallem den Trick mit der progress - Methode finde ich sehr elegant, dass hätte ich auch wesentlich umständlicher gelöst

    Was du jedoch in der Game_Screen Methode treibst ist mir noch zu großen Teilen schleierhaft.
    Deine Grundidee ist doch alle DamageDisplay-"Objekte" in ein Array zu schreiben um das von dir angesprochene Problem zu vermeiden:
    Zitat Zitat von -KD-
    Wenn du den Code hingegen mehrfach ausführst, wird die Variable neu zugewiesen und der alte Sprite wird vom GarbageCollector gelöscht. Darum verschwindet die Grafik nach wenigen Sekunden.


    Code:
    class Game_Screen
      def damage_displays
        displays = (@damage_displays ||= [])
        displays.delete_if {|display| display.finished?}
        displays
      end
    end
    1) Woher kommt denn @damage_displays, die wird in deinem Code doch garnicht angelegt, oder täusche ich mich da? Außerdem ist der " ||= "-Operator den du hier bentuzt ist eine Veroderung von [] (einem leeren Array (?)) mit sich selbst. Bewirkt das etwas? Eine Veroderung mit Null verändert doch nichts. Wenn das Ergebnis vorher 1 oder 0 war kommt doch entsprechend 1 oder 0 wieder raus.
    2) Die zweite Zeile räumt alle fertigen display aus dem Array.
    3) Aber was macht die dritte Zeile Code hier? Ist das eine Methode? Wo ist die denn definiert? Oder ist das die Variable displays, aber du machst ja nichts damit, oder?!

    Code:
    class Game_Screen
      alias update_damage_displays update
      def update
        update_damage_displays
        @sprite_damage_displays ||= []
        displays = $game_screen.damage_displays
        # lösche alle Displays die nicht mehr benötigt werden
        @sprite_damage_displays[displays.size..-1].each {|display| display.dispose}
        @sprite_damage_displays = @sprite_damage_displays[0...displays.size]
        # update den Rest
        displays.each_with_index {|display, index| 
          @sprite_damage_displays[index] ||= Sprite_DamageDisplay.new
          @sprite_damage_displays[index].refresh(display)
        }
      end
    
      def dispose
        @sprite_damage_displays.each {|d| d.dispose}
        @sprite_damage_displays.clear
      end
    
    end
    update-methode:
    4) Über update_damage_displays rufst du doch einfach die alte update-Methode aus Game_Screen auf, stimmts? (verstehe die alias Funktion noch nicht so recht, die bentutzt man ja um quasi die alte Methode zu erhalten und neue Befehle hinzu zu fügen, weil man hier update nicht komplett überschreiben will)
    5) Jetzt bin ich wieder verwirrt weil doch " @sprite_damage_displays ||= []" nichts tut, @sprite_damage_displays ist ja einfach leer?
    6) "@sprite_damage_displays[displays.size..-1].each {|display| display.dispose}" Druchläuft das @sprite_damage_displays-Array und führt auf alle display die dispose Methode aus.
    7)
    Code:
    displays.each_with_index {|display, index| 
          @sprite_damage_displays[index] ||= Sprite_DamageDisplay.new
          @sprite_damage_displays[index].refresh(display)
        }
    Was passiert denn hier? Also der Sinn dahinter ist, entweder einen neuen Spirte zu zeichnen oder einen bestehenden zu refreshen, aber die Syntax macht mich fertig.

    Ich hoffe ich geh dir mit meiner Fragerei nicht zu sehr auf die Nerven Würds halt gern verstehen.

  8. #8
    Zitat Zitat
    1) Woher kommt denn @damage_displays, die wird in deinem Code doch garnicht angelegt, oder täusche ich mich da? Außerdem ist der " ||= "-Operator den du hier bentuzt ist eine Veroderung von [] (einem leeren Array (?)) mit sich selbst. Bewirkt das etwas?
    Ja =P Weil in Ruby die boolsche Algebra nicht nur auf boolschen Werten, sondern auf allen Objekten arbeitet. Ein Objekt zählt dann als "wahr", wenn es nicht false und nicht nil ist. Wenn du also schreibst (5 or 3) dann ist das Ergebnis ein wahrer Wert, nämlich 5 (nicht true!). Genauso ist das Ergebnis aus (nil or 2) gleich 2, das Ergebnis aus (1 and "Hallo") ist "Hallo" usw.
    Die Operation
    Code:
    x ||= y
    # ist das selbe wie
    x || (x = y)
    # und das ist das selbe wie
    if x then
      x
    else
      x = y
    end
    In dem obigen Codebeispiel heißt das:
    Code:
    displays = (@damage_displays ||= [])
    # ist dasselbe wie
    if @damage_displays then
      displays = @damage_displays
    else
      @damage_displays = []
      displays = @damage_displays
    end
    Nur in 1 Zeile gegossen. Zugegeben, dass ist vielleicht nicht so offensichtlich. Aber diese Art den ||= Operator zu verwenden ist in Ruby üblich und verbreitet. Wenn man es einmal verstanden hat ist es ja auch ganz logisch. x ||= y heißt eben: Setze x auf y wenn x noch keinen Wert hat (oder false ist).

    Zitat Zitat
    3) Aber was macht die dritte Zeile Code hier? Ist das eine Methode? Wo ist die denn definiert? Oder ist das die Variable displays, aber du machst ja nichts damit, oder?!
    Die dritte Zeile ist einfach der Rückgabewert der Methode. Man hätte auch return displays schreiben können. Aber in Ruby ist immer der letzte ausgeführte Wert einer Methode der Rückgabewert. Das return ist also optional. Wer imperative Sprachen wie Java oder C gewohnt ist, mag das fehlende return ja vermissen. Aber gerade in funktionalen Sprachen ist das schon lange üblich, dass return wegzulassen. Du schreibst in der Mathematik ja auch nicht f(x) = return x**2.

    Zitat Zitat
    4) Über update_damage_displays rufst du doch einfach die alte update-Methode aus Game_Screen auf, stimmts? (verstehe die alias Funktion noch nicht so recht, die bentutzt man ja um quasi die alte Methode zu erhalten und neue Befehle hinzu zu fügen, weil man hier update nicht komplett überschreiben will)
    Genau. Hier ist ein kurzes Tutorial zu dem alias Befehl. In aller Kürze: Alias kopiert eine Methode unter einem neuen Namen. Die neue Methode verhält sich dabei exakt wie die alte. Der Trick ist jetzt, die alte Methode zu überschreiben und ihr ein neues Verhalten zu definieren und dabei die neue Methode aufrufen um das alte Verhalten einzufügen.

    Zitat Zitat
    5) Jetzt bin ich wieder verwirrt weil doch " @sprite_damage_displays ||= []" nichts tut, @sprite_damage_displays ist ja einfach leer?
    Wieder genau dasselbe wie oben. Im Grunde genommen hatte ich nur keine Lust, auch noch die initialize Methode zu erweitern.

    Zitat Zitat
    6) "@sprite_damage_displays[displays.size..-1].each {|display| display.dispose}" Druchläuft das @sprite_damage_displays-Array und führt auf alle display die dispose Methode aus.
    Nur die überschüssigen. Wenn der displays Array nur 5 Game_DamageDisplay Elemente beinhaltet, in unserem @sprite_damage_displays Array aber 8 Sprites liegen, dann müssen 3 Sprites gelöscht werden (die ja überschüssig sind). Im Grunde genommen musst du halt dafür sorgen, dass für jeden Game_DamageDisplay auch ein Sprite_DamageDisplay zur Verfügung steht. Wenn ein Game_DamageDisplay hinzukommt, musst du auch einen Sprite hinzufügen. Wird einer gelöscht, musst du auch einen Sprite löschen.
    In den Standardscripten des Makers wird so etwas immer vermieden, in dem einfach feste Limits gesetzt werden. z.B. legt der Maker immer 50 Pictures an, egal ob alle 50 Pictures gerade gebraucht werden. Dadurch spart sich der Maker immer den Array mit Sprite_Picture "synchron" mit dem Array mit Game_Picture zu halten. Der Nachteil: Der Nutzer darf nicht mehr als 50 Pictures anzeigen, obwohl das technisch eigentlich kein Problem wäre. Durch diesen etwas komplizierteren Code erlauben wir dem Benutzen, beliebig viele DamageDisplays anzuzeigen.

    Zitat Zitat
    Was passiert denn hier? Also der Sinn dahinter ist, entweder einen neuen Spirte zu zeichnen oder einen bestehenden zu refreshen, aber die Syntax macht mich fertig
    1. Wenn noch kein Sprite_DamageDisplay an Position index steht, dann erzeuge einen.
    2. Rufe danach refresh auf und übergib das Game_DamageDisplay dem Sprite als Parameter. Der Sprite sollte in der Methode dann die Schadensanzeige zeichnen.

    Die dispose Methode muss btw. auch gealiased werden, das hatte ich ganz vergessen.

  9. #9
    Find in All Sections"-Suchfunktion:
    Strg+Shift+F

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •