PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [VX Ace] Schadens-Popups und Performancefragen



Fuxfell
12.11.2012, 19:38
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 :) )




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

Cornix
12.11.2012, 19:49
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.)

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.

Fuxfell
16.11.2012, 22:49
Danke Cornix! Deine Lösung ist wesentlich eleganter, als das was ich mir da zusammengeschustert hatte :D 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:

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

Cornix
17.11.2012, 10:52
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.

Fuxfell
17.11.2012, 13:12
Der ist ziemlich simpel.


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

-KD-
17.11.2012, 13:41
(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:

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

Fuxfell
17.11.2012, 14:45
Das entpuppt sich ja als immer komplexer hier :D 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? :D

@-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 :D

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:

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.




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


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)

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.

-KD-
17.11.2012, 17:35
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

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:

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


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.


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 (http://www.rpg-studio.de/forum/board167-rgss-rgss-2/30406-alias/) 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.


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.


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.


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.

Linkey
18.11.2012, 09:45
Find in All Sections"-Suchfunktion:
Strg+Shift+F