Du bist nicht angemeldet.
[EDIT Ron]
Weiterfuehrung aus dem Thread "Blitzmax - Listen/Arrays"
http://www.gamezworld.de/phpforum/viewtopic.php?id=13395
Hier geht es eher um Gameloops und wie man allgemein mit Updates und Ereignissen umgeht
[/EDIT Ron]
Dafuer empfehle ich dir von dig
Import "base.util.input.bmx"
Dann kannst du mit MouseManager.ChangeStatus() den aktuellen Zustand abspeichern (machst Du einmal im "loop"). Denn "MouseHit()" was BlitzMax anbietet, setzt sich durch die Abfrage zurueck. -- doof wenn man das in einem "Loop" mehrfach abfragen will.
bye
Ron
Offline
Sooo,
eher nur für dich Ron, hier mal das was ich jetzt gemacht habe.
Also ich habe es hinbekommen das es so wie ich mir das vorstellte zumindest ersteinmal funktioniert
Der Code naja ... gibt bestimmt genug leute die sich totlachen würden wenn se sich das anschauen
(Es ist schlimmer geworden als was ich vorher gedacht hätte)
http://www.workupload.com/file/N6GnhE57 (zip mit exe,png und bmx)
als kontrollbild:
So sollte der Screen aussehen mit geöffnetem Untermenü (Maus ist da bei "Bank")
Derzeit hat ja auch nur "Zur Stadt" ein Untermenü
Davon abgesehen das ich jetzt noch nicht wirklich in's Framework geschaut habe, kannst du mir ja aber ansonsten vielleicht noch ein paar tipps geben.
Die verwendeten Bilder sind dieses mal alle CCO also Public Domain.
gruß
sushi
Nachtrag: Das Bild da oben links hat eigentlich auch eine Schwarze Kontur, die wird aber aus irgendwelchen gründen nicht dargestellt.
Beitrag geändert von sushiTV (05.04.2014 20:58)
das Leben ist ein scheiß Spiel, hat aber ne geile Grafik
Offline
Print "Create Image"
bild_bg = CreateImage (800, 600)
bild_bgp1 = CreateImage (254, 154)
Print "Load Image"
bild_bg = LoadImage ("bg_01.png")
bild_bgp1 = LoadImage ("bgp_weingut.png")
Das ist unnoetig - du musst kein "createImage" machen. CreateImage erzeugt ein neues Bild (mit "Muellpixeln" drin)..
es reicht das "LoadImage".
Sleep 20
Nimm lieber "Delay 20" - dann laeufts auch unter Linux . Generell wuerde ich dir aber hier zum Framework raten. - Du vermengst noch "Logik" und "Zeichnen".
Eine "GameLoop" sieht immer so aus:
While not SpielbeendenVariable = True
Update()
Render()
Wend
Natuerlich kann man jetzt - wie bei unserem Framework die Updates und Renders auf eine bestimmte Menge pro Sekunde begrenzen (um die CPU und GPU zu schonen)
Ich bau jetzt mal den "notwendigen" Teil um - und poste gleich den Code hier rein. (Ich baue nur den Teil um, der notwendig ist um das zum laufen zu bekommen.
bye
Ron
Offline
Ich habe das jetzt ein wenig aufgeraeumt - da war einiges wohl doppelt.
Manches sieht nun aus wie "doppelte Arbeit". Man kann bestimmte Dinge ("Menues verschieben" durchaus auch bei "Render" machen ... aber das ist Geschmackssache).
Generell musst du Dinge aufsplitten: Logik ("Sachen verkaufen", Partikel erzeugen/bewegen/... ) und Rendern (also Buttons zeichnen usw.)
Ich habe jetzt exemplarisch bestimmte Zeilen von Dir mit "Dig-Code" ersetzt. Rein damit du ein wenig "Geschmaekle" dran finden kannst .
'
'My first BlitzMax program
'Datum: 2014.04.05
'Autor: ~s~
Import "base.util.helper.bmx" 'hilfsfunktionen
Import "base.util.event.bmx" 'mit neuem DigUpdate nicht noetig, hab das beim deltatimer vergessen :D
Import "base.util.deltatimer.bmx"
'Include BRL.PNGLoader
Graphics 800, 600, 0, 0
Global exitApp:int = FALSE 'spiel beenden?
Global bild_bg:TImage
Global bild_bgp1:TImage
Global Ustatus:Int = - 1
Global MenuStatus:Int = - 1
Global Hauptmenu:TMenu[]
Global Stadtmenu:TMenu[]
Global Dummy:String
Type TPlayer
Field Home:String
Field Konto:Float
Field Keller:TKeller
End Type
Type TKeller
Field Flaschen:Int
Field Lagerplatz:Int
End Type
Type TMenu
Field x:Int
Field y:Int
Field b:Int
Field h:Int
Field name:String
Field status:Int
Method Draw()
If status = 0 Then
SetColor(232, 222, 191)
ElseIf status = 1 Then
SetColor(223, 144, 26)
ElseIf status = 2 Then
SetColor(243, 202, 11)
EndIf
DrawText(name, x, y)
'Farbe wieder zuruecksetzen - nicht vergessen :D
SetColor(255, 255, 255)
End Method
End Type
Function MenuInit()
Local x, y, h, b:Int
x = 75
y = 180
h = 19
b = 120
For i = 0 To 5
Local NewMenu:TMenu
NewMenu = New TMenu
NewMenu.x = x
NewMenu.y = y + (i * h)
NewMenu.b = b
NewMenu.h = h
NewMenu.status = 0
Hauptmenu:+ [NewMenu]
Next
x = 75 + h
y = 180 + 19
h = 19
b = 120
For i = 0 To 6
Local NewMenu1:TMenu
NewMenu1 = New TMenu
NewMenu1.x = x
NewMenu1.y = y + (i * h)
NewMenu1.b = b
NewMenu1.h = h
NewMenu1.status = 0
Stadtmenu:+ [NewMenu1]
Next
Hauptmenu[0].name = "Zur Stadt"
Hauptmenu[1].name = "Weinberg"
Hauptmenu[2].name = "Schuppen"
Hauptmenu[3].name = "Lagerhaus"
Hauptmenu[4].name = "Kellerei"
Hauptmenu[5].name = "Wohnhaus"
Stadtmenu[0].name = "Sabotage"
Stadtmenu[1].name = "Bank"
Stadtmenu[2].name = "Arbeitsamt"
Stadtmenu[3].name = "Grosshandel"
Stadtmenu[4].name = "Einzelhandel"
Stadtmenu[5].name = "Rathaus/Amt"
Stadtmenu[6].name = "Werbebuero"
End Function
Function MenuDraw()
Local x:int = 75, y:int = 180, h:int = 19, b:int= 120
Hauptmenu[0].Draw()
If Ustatus = 0 And MenuStatus = 1 Then
MenuStatus = -1
SetColor(129, 8, 39)
DrawRect(x, y + h, b, h * 6)
y = y + (7 * h)
'Farbe wieder zuruecksetzen - nicht vergessen :D
SetColor(255, 255, 255)
For i = 1 To 5
Hauptmenu[i].y = y + (i * h)
Hauptmenu[i].Draw()
Next
For i = 0 To 6
Stadtmenu(i).Draw()
Next
ElseIf MenuStatus = 1
MenuStatus = - 1
SetColor(129, 8, 39)
DrawRect(x, y + h, 120, 380)
'Farbe wieder zuruecksetzen - nicht vergessen :D
SetColor(255, 255, 255)
For i = 1 To 5
Hauptmenu[i].y = y + (i * h)
Next
EndIf
End Function
'spiellogik aktualisieren
Function UpdateWorld:int()
'refresh mouse/keyboard
MouseManager.ChangeStatus()
KeyManager.ChangeStatus()
For i = 0 To 5
If THelper.MouseIn(Hauptmenu[i].x, Hauptmenu[i].y, Hauptmenu[i].b, Hauptmenu[i].h) Then
Hauptmenu[i].status = 1
If MouseManager.IsDown(1) Then
Hauptmenu[i].status = 2
Ustatus = i
MenuStatus = 1
EndIf
Else
Hauptmenu[i].status = 0
EndIf
Next
If Ustatus = 0 Then
For i = 0 To 6
If THelper.MouseIn(Stadtmenu[i].x, Stadtmenu[i].y, Stadtmenu[i].b, Stadtmenu[i].h) Then
Stadtmenu[i].status = 1
If MouseManager.IsDown(1) Then
Stadtmenu[i].status = 2
' MenuStatus = 1
EndIf
Else
Stadtmenu[i].status = 0
EndIf
Next
EndIf
'spiel beendbar mit ESC
If KeyManager.IsHit(KEY_ESCAPE) then exitApp = TRUE
End Function
Function RenderWorld:int()
DrawImage bild_bg, 0, 0
DrawImage bild_bgp1, 15, 15
SetColor(232, 222, 191)
DrawText (Dummy, 280, 47)
'Farbe wieder zuruecksetzen - nicht vergessen :D
SetColor(255, 255, 255)
MenuDraw()
For i = 0 To 5
Hauptmenu[i].Draw()
Next
If Ustatus = 0 Then
For i = 0 To 6
Stadtmenu[i].Draw()
Next
EndIf
Flip 0 'alles gezeichnete "an die grafikkarte schicken"
End Function
'wir sagen dem deltatimer, was die update- und renderfunktionen sind
GetDeltaTimer()._funcUpdate = UpdateWorld
GetDeltaTimer()._funcRender = RenderWorld
'und dass wir 30 Logikupdates und 60 Renders pro Sekunde wollen
GetDeltaTimer().Init(30, 60)
'Sachen initialisieren
MenuInit()
Dummy = "Datum: Jan 1980 Ort: Weingut Sachsen Konto: 100.000"
Print "Load Image"
bild_bg = LoadImage ("bg_01.png")
bild_bgp1 = LoadImage ("bgp_weingut.png")
'game loop
Repeat
'run the deltatimer's loop
'which runs the hooked update/render function
GetDeltaTimer().loop()
'falls du mit events arbeitest - muessen die natuerlich hier
'abgearbeitet werden
'EventManager.update()
Until AppTerminate() Or exitApp
End
Ich hoffe Du fuehlst dich jetzt nicht von mir ueberrannt.
Uebungstodos fuer dich:
Statt in Updateworld diese For-Schleife mit den Menuepunkten und Ueberpruefungen zu haben: Lege eine "Method Update:int()" fuer den Type TMenue an ... und rufe dann nur noch fuer menues "xyzmenu.Update()" auf.
Jedes Menue fuer sich selbst checkt dann ab, ob die Maus drin ist oder nicht.
Versuche sozusagen die Objekte fuer sich selbst sorgen zu lassen. Dann weisst Du spaeter auch, an welcher Stelle die Menuepunkte veraendert/aktualisiert werden und es liegt nicht mehr verstreut im Code herum.
bye
Ron
Offline
Jedes Menue fuer sich selbst checkt dann ab, ob die Maus drin ist oder nicht.
Versuche sozusagen die Objekte fuer sich selbst sorgen zu lassen. Dann weisst Du spaeter auch, an welcher Stelle die Menuepunkte veraendert/aktualisiert werden und es liegt nicht mehr verstreut im Code herum.
Genau das ist mein Problem zu verstehen wie man etwas "vernünftig" coded
Wenn ich da hier und da in TVT-Code geschaut habe und versucht habe das zu verstehen, hatte ich aber manchmal schon das gefühl das das eine oder andere ganz alleine wie von "zauberhand" funktioniert ....
Ich denke das meine blickrichtung und denkweise da ganz einfach auch falsch ist.
Ich denke ich muss alles Organisieren im Code ....
Und das wird dann meist halt sehr Unorganisiert und verstreut
Ich fühle mich gar nicht "überrant" im gegenteil, ich bin erstaund wie du mit ein paar wenigen änderungen das aber schon deutlich besser machen kannst und das in kurzer zeit. (ich hoffe jetzt zumindest das dies dich nicht zu viel zeit gekostet hat.=
Und besonders Interessant dabei ist ja jetzt .... Anfänger Coding und nutzen des Frameworks und das auch im Vergleich
Ich muss mir das aber trotzdem auch noch etwas genauer betrachten
gruß
sushi
Beitrag geändert von sushiTV (05.04.2014 22:05)
das Leben ist ein scheiß Spiel, hat aber ne geile Grafik
Offline
Ok ... also Du hast ja nun schon diese Typen angelegt.
Wenn Du nun mit "Methoden" arbeitest (Das sind "Funktionen" die fuer ein spezielles Objekt - also bspweise ein spezifisches Menu - aufgerufen werden), dann bietet es sich dort an mit "Update" und "Render" (oder "Draw") loszulegen.
Innerhalb von der Methode "Update" des Types TMenue wuerdest du also fuer diesen Menuepunkt spezifische Updates machen: heisst schauen ob die Maus drueber ist, ob geklickt worden ist usw.
Das alles erlaubt dann spaeter naemlich etwas ganz wichtiges: "Ueberschreiben". Wenn du verschiedene Menuearten hast - behandeln manche Menuearten ja die Maus ganz anders. Sie leuchten auf oder was auch immer. Und bei denen ueberschreibt man dann einfach diese "Method Draw:int()" und macht dort alles anders
Deine Blickrichtung... ist nicht falsch, nur noch nicht richtig ausgerichtet - dass kommt mit der Uebung von ganz alleine.
In der "UpdateWorld" kannst Du dann bspweise die "Zeit" messen ... sind X Millisekunden vorbei: naechste Runde. Oder alternativ: "Naechste Runde"-Text zum anklicken und der erhoeht die Runde.
... aber er sagt nicht einfach "round = round +1", nein es wird die Funktion "NextRound" aufgerufen. In dieser machst Du dann die rundenspezifischen Sachen ... "GenerateNewWeather()" usw.
Immer so, dass sich alles gut finden laesst.
Edit: und wenn du dich dann immer weiter damit "auskennst", kannst Du dich dann an das "Screensystem" des Frameworks ranwagen. Denn gerade fuer so ein Spiel ist das natuerlich optimal.
Screens sind sozusagen "Bildschirme" - Startbildschirm, Levelladebildschirm. Hier kann es aber auch sein: "Hauptmenue", "Stadt", "Lager" ... und fuer jeden dieser Bildschirme kannst Du dann eigene "Update" und "Render" bestimmen - oder aber (das wuerde ich dann noch erklaeren - oder ein Beispiel machen) fuer bestimmte Dinge "Vorlagen" anlegen. Alle "Menu"bildschirme basieren dann auf einem "TMenuScreen" der immer einen blauen Hintergrund zeichnet oder anderes.
Wenn man zwischen den Bildschirmen wechselt kann man "Effekte" einbinden (Schwarzblende oder aehnliches). Vereinfacht vieles. Statische Elemente ("Dekoration") laesst sich dann ohne groesseren Aufriss in die "Layer" eines "Screens" packen. Andere Frameworks machen das zumindest so ... bei TVTower sind wir noch nicht soweit (das muss ich noch umstellen).
So... bastel Du mal weiter.
PS: MouseManager.GetX(), MouseManager.GetY() - und MouseManager.getScrollwheelMovement() sind schoene Hilfsfunktionen. Auch "IsClicked(1 oder 2)" (wartet aufs "loslassen" der Maustaste) kann dir ganz hilfreich sein.
Ich kommentiere jetzt noch ein paar Framework-Codes - vielleicht hilft es dir dann weiter.
bye
Ron
Offline
Innerhalb von der Methode "Update" des Types TMenue wuerdest du also fuer diesen Menuepunkt spezifische Updates machen: heisst schauen ob die Maus drueber ist, ob geklickt worden ist usw.
das ist halt das was ich nicht so wirklich verstehe.
Ich habe doch folgendes gemacht:
eine function mousehandling() welche in sich dann alle möglichen zustände prüft und dann eventuelle variablen ändert und reaktionen auslößt.
(okay, ich gebe zu, das die reaktionen wohl allgemein dann eher vom Typ "Render" waren)
Das was du meinst, ist doch aber das ein angelegtes objekt dann selber checkt ob sich die mouse in "ihrem" (ist ein objekt eine sie? *lach*) wirkungsbereich befindet oder nicht.
wenn dieses objekt mit seinen eigenschaften von ausdehnung/größe oder standort/platzierung auf dem bildschirm immer selbständig checkt in wie weit es von der Mouse berührt wird (auf ereignisse reagieren muss) ....
dann ist das doch genau die gegenteilige herrangehensweise als was ich das getan habe?
Ich habe mit den Augen der Mouse gesehen *blinkerblinker*
aber wie löße ich denn so ein selbstständiges überwachen aus .... das ist mir alles ein rätzel (das objekt schaut die Mouse an ? )
gruß
sushi
Beitrag geändert von sushiTV (05.04.2014 22:36)
das Leben ist ein scheiß Spiel, hat aber ne geile Grafik
Offline
ahhhhh ......
ich lege eine methode update eines objektes an und löße diese in der loop aus?
in der update wird geprüft ob die mouse sich im eigenen bereich befindet und eventuell ein butten gedrückt ist etc. .....
je nach dem kann ich reagieren und darauf ein "render" des objects aus der update auslösen ?
ich muss halt in der gameloop oder so nur permanent bei allen objekten dann das update() auslößen ?
gruß´~s~
Ich korrigiere selber:
updates in der update()
gerendert wird dann über render()
deswegen wohl auch diese trennung, damit das nacheinander erfolgt und nicht quer durcheinander
Beitrag geändert von sushiTV (05.04.2014 23:05)
das Leben ist ein scheiß Spiel, hat aber ne geile Grafik
Offline
ja in der "gameloop" muss immer artig geupdated werden.
Damit du die "gameloop" aber nicht selber erweiterst, haben wir ja die Funktionen "UpdateWorld" und "RenderWorld" erstellt.
Innerhalb der UpdateWorld muss alles aktualisiert und bei RenderWorld alles gezeichnet werden.
Alles natuerlich im Sinne von: "alles aktive".
auf TMenue gemuenzt:
Innerhalb der UpdateWorld()-Funktion rufst du fuer jeden Menuepunkt "update" auf (weil du ja derzeit das "Menue" aktiv hast - irgendwo darstellst und somit auf Mausklicks reagieren willst).
Innerhalb dieser "Update()"-Methoden von TMenue ueberpruefst du dann ob was passiert ist (Maus drueber oder so).
An deinem Code:
Function UpdateWorld:Int()
...
For i = 0 To 5
If THelper.MouseIn(Hauptmenu[i].x, Hauptmenu[i].y, Hauptmenu[i].b, Hauptmenu[i].h) Then
Hauptmenu[i].status = 1
If MouseManager.IsDown(1) Then
Hauptmenu[i].status = 2
Ustatus = i
MenuStatus = 1
EndIf
Else
Hauptmenu[i].status = 0
EndIf
Next
...
End Function
wird dann:
Function UpdateWorld:Int()
...
For i = 0 To 5
HauptMenu[i].Update()
Next
...
End Function
Natuerlich muss der nun fehlende code wohin... und zwar bei TMenue in eine Updatemethode
Type TMenue
...
Method Update:Int()
'standardmaessig ausschalten
status = 0
'wenn die maus nicht drin ist - nix machen
If not THelper.MouseIn(x, y, b, h) Then return FALSE
'du kannst mit "self." explizit hinweisen, was du meinst
'".status" (also nur punkt) wuerde explizit eine "global" variable
'ansprechen
self.status = 1
If MouseManager.IsDown(1) Then
self.status = 2
Ustatus = i
MenuStatus = 1
EndIf
End Method
...
Du siehst gleich, wie ich die "if"-Verschachtelung entfernt habe - bringt manchmal mehr Uebersicht, laesst sich aber nicht immer so machen (wenn man fuer "if" viel zu tun hat und fuer "else" aber auch). Wenn es aber nur darum geht, "wenn etwas der fall ist, dann mache" - bietet es sich an einfach abzubrechen, wenn es nicht der fall ist (Ausschlusskriterium).
Man kann diese for-schleifen auch anders schreiben
for local i:int = 0 to 5
HauptMenu[i].Update()
Next
for local i:int = 0 to HauptMenu.length -1 'arrays fangen ja mit 0 an
HauptMenu[i].Update()
Next
for local menu:TMenu = EachIn HauptMenu
menu.Update()
Next
Damit musst Du nicht immer wissen, wieviele Menues du drin gespeichert hast.
EDIT: Warum willst du alles vom Standpunkt der Maus aus betrachten? Was passiert dann, wenn du auch auf die Tastatur achten moechtest? Oder Touchscreens?
Statt "if mousedown then status=1;ustatus=2..." kannst Du dir auch weitere Hilfsmethoden schreiben: "SetActive()" - die dann den status aendert. Vorteil ist dann: du hast eine zentrale Anlaufstelle, den Status zu aendern. Denn dies kann mittels Mausklick ausgeloest werden - oder mittels eines Wetterereignisses .
Ein "Menupunkt.SetActive()" kann natuerlich auch eine globale "welcher menupunkt ist aktiv"-variable ueberschreiben. Anstatt also in jedem Menuepunkt zu speichern, ob er "aktiv" ist, speichert man einfach einmalig WELCHER punkt aktiv ist (oder "null" fuer keinen). Jeder Menupunkt kann dann mit "if self = aktiverMenupunktGlobaleVariable then ..." ueberpruefen ob er der aktive ist. Verkuerzt macht man das dann in einer weiteren Helfermethode:
Methode IsActive:int()
return (self = aktiverMenupunktGlobaleVariable)
End Method
und schwupps kann man mit "Menupunkt.IsActive()" schauen ob der Menupunkt der Aktive ist. Wenn man hingegen bei jedem Menupunkt speichert, ob er aktiv ist - kann man im Eifer des Gefechts vergessen einen Punkt als nichtaktiv zu setzen und schwupps hat man mehrere aktive ... bloed .
So - und bevor nun dein Kopf raucht - experimentier ein wenig weiter und lass dich nicht vom alten Quatschkopf Ronny so zutexten.
Freundin kommt eh bald von Arbeit - da ists hier vorbei mit der Computerei.
bye
Ron
Offline
Okay,
ich danke dir, ich denke das ich auf grund des codebeispiels und der erklärung und das mir da ebend selber schon ein licht aufgegangen ist und du das jetzt nochmals mit code belegt hast .....
ich das jetzt so ein bissel begriffen habe ...
das hat bisher keine erklärung oder code geschaft
das dauerhaft umzusetzen wird nicht direkt gelingen, wie du ja sagtest, kommt einiges auch erst mit übung
thx
~sushi~
das Leben ist ein scheiß Spiel, hat aber ne geile Grafik
Offline
Hab obigen Beitrag noch einen "Edit" verpasst.
Was sicher auch noch ein kleines Uebungsbeispiel fuer dich ist:
mit "HideMouse()" kannst du den Systemmauscursor verschwinden lassen... Wenn man nun an der Stelle "MouseManager.GetX(), MouseManager.GetY()" ein Mauscursor-Bild zeichnet - hat man einen schoenen InGame-Cursor. Wenn die "Spitze" des Mauszeigers dann nicht exakt die Position einnimmt - verschiebe die Grafik einfach ein wenig (...GetX() - 5, ... GetY() -10).
Du hast Dir uebrigens mit "Menuepunkten" den optimalen Kandidaten fuer ein Eventsystem rausgesucht, aber dazu spaeter mal mehr (wenn du magst).
bye
Ron
Offline
Hmmm,
also das erste bei "Winzer" war ja jetzt das es für's erste ein "Klone" bleiben sollte ..... also "Screen" erstellen (auch das hätte ich mit DrawRect machen können) ....
und dann Halt das erste Handling im Spiel und das sind Menüs .... und darauf dann die Bildschirme also "Räume" .... etc. und so weiter ....
so war jetzt mein gedanke ....
Grundlegend ist doch ersteinmal der hauptaufbau (fenster, menü - steuerung) ?
Für das spiel letzten endes interessanter ist alles das was dahinter abläuft .... nur das kann es nicht wenn man es ja nicht steuern kann
Menüpunkte und Eventsystem ?
immer raus damit
nachtrag:
okay, dein nochmaliger nachtrag deines beitrags hat den noch etwas mehr erweitert und interessante aspekte beleuchtet
das muss ich erst noch kappieren
ich denke mal deine freundin ist nu auch da, dann bis bald
Beitrag geändert von sushiTV (05.04.2014 23:40)
das Leben ist ein scheiß Spiel, hat aber ne geile Grafik
Offline
EDIT: Warum willst du alles vom Standpunkt der Maus aus betrachten? Was passiert dann, wenn du auch auf die Tastatur achten moechtest? Oder Touchscreens?
Ich habe darüber heute abend auch nachgedacht.
(vielleicht habe ich ja deswegen keine freundin?)
Ich denke das es für mich als viel unaufwendiger und weniger rechenlastig erschien der mouse zu folgen und nur wenn diese "sensible" zonen erreicht auch etwas zu verändern etc. ....
Aber das vernünftige ist natürlich das die Objekte selber die überprüfung machen - vorallem programmiertechnisch .... stell dir mal vor das winzer hätte 250 buttons die man anklicken kann? aus der sicht der mouse das zu checken? ein irrsinn dann doch lieber das aller paar millisecunden alle objekte checken ob die mouse bei ihnen ist und eventuell gedrück oder nicht
wobei mir das auslößen des Update() auch noch nicht 100% klar ist ....
aber ich denke, das du da eventuell mit dem Thema Eventsystem da vielleicht nocht etwas weiter ausholen wolltest.
gruß
sushi
Beitrag geändert von sushiTV (06.04.2014 00:19)
das Leben ist ein scheiß Spiel, hat aber ne geile Grafik
Offline
vielleicht habe ich ja deswegen keine freundin?
Dafuer gibt es meist einfachere Gruende: Man ist seltsam, spricht keine neuen Leute an und verkehrt mit einem "festen" Freundeskreis, man sieht einfach scheisse aus - oder: man verdaut noch die letzte Beziehung bzw. ist noch im "mit der Ex-Vergleichen"-Modus. So Scherz beiseite.
Das mit dem Eventsystem ist natuerlich keine "Magie" - wie du schon richtig erkannt hast, muss ja alles irgendwie ein "Update" haben und immer schoen ausgefuehrt werden. Das "alles" kann man dann wieder filtern. Bei den GUI-Elementen uebergibt man dem GUIManager bestimmte "States" und es werden dann alle GUIElemente "uebersprungen" die sich fuer diesen State interessieren. Dies kann man nun so bewerkstelligen: jeder "State" hat eine Liste mit interessierten Elementen - erschwert das Management wenn ein Objekt mal mehr mal weniger States beobachtet. Oder man hat eine Liste mit GUIElementen - und fragt pro Durchgang alle Elemente ab. Hat das Element Interesse am "State", tut es was, ansonsten wird es uebersprungen.
Eventuell kannst Du dir nun denken, dass beide "Ansaetze" sogenannte "Tradeoffs" besitzen. Wenn Du jetzt 1 Million Elemente aber nur 2 "States" benutzt - ist es bloed immer 1 Mio Elemente abzufragen, dann waere es sinniger zwei Listen zu fuehren und immer nur die gewollte abzufragen - man spart da ja 500.000 Abfragen.
Bei einem Partikelsystem wuerde man also statt einer grossen Liste eher alles so gruppieren, dass ein "ParticleEmitter" diese in einer eigenen Liste gruppiert. Problem waeren dann hier aber "Kollisionen" - wenn also ein PartikelA von EmitterA mit PartikelB von EmitterB kollidieren soll. Da EmitterA EmitterB nicht kennt - und es ja auch Emitter120440 geben koennte, muss hier wieder geschaut werden, ob es sich immer lohnt so vorzugehen oder ob nicht doch ein anderer Ansatz praktischer waere. Im Fall von Partikeln waere es sowas wie eine "Quadrantenliste" - ein Partikel ist immer zusaetzlich in einer Liste des jeweiligen Quadranten in dem er sich befindet - bzw entsprechend seiner Groesse koennen dass auch "mehrere Quadranten" sein. Damit ist dann gewaehrleistet, dass der Partikel immer nur einen "Bruchteil" der gesamten Partikel "gegenchecken" muss. Tradeoff ist hier: mit jeder Bewegung muss er ja abaendern in welchen Listen er so gespeichert ist (hier kommt hinzu, dass man da wohl besser mit Arrays arbeiten wuerde statt mit Listen - bzw mit Arrays die man aller X Sekunden "bereinigt" um Luecken zu schliessen).
Ok zurueck zum Thema "Menues": Jeder Menupunkt wuerde in einem Eventsystem trotzdem sein "Update" ausfuehren. Irgendwie willst Du ja auf "Klicks" mit irgendwas reagieren. Das jetzt in einer einzigen grossen Funktion abzurufen ist umstaendlich und fehleranfaellig. Deswegen kannst du verschiedene Moeglichkeiten probieren:
a) du gibst jedem TMenu ein "Field clickFunc:int()" - dann kannst du jedem Menupunkt eine eigene "Klickfunktion" zuweisen (kennt man von VisualBasic, Delphi, wenn man doppelt auf ein GUIElement geklickt hat - und die "onclick"-Funktion kreiiert worden ist)
Type TMenu
'eine Funktion die int zurueckgibt und das
'geklickte Menu im Parameter erwartet
'praktisch um "mehrere Menus" in einer Funktion
'abarbeiten zu koennen
Field onClickFunc:int(sender:TMenu)
...
Method Update:int()
if angeklickt then onClick()
End Method
'extra onClick-Methode damit man die in einem
'erweiterten Menutyp ueberschreiben koennte
Method onClick()
'ist eine funktion "verknuepft", dann ruf die auf
'menu selbst ist der "sender"
if func then onClickFunc(self)
End Method
...
End Type
'beispiel click handler - der nicht innerhalb des
'TMenu stehen muss - aber auch koennte
Function menuOnClick:Int(sender:TMenu)
'jemand hat die Funktion nicht korrekt aufgerufen
if not sender then return False
print sender.name + " wurde angeklickt"
End Function
'beispiel click handler - der nicht innerhalb des
'TMenu stehen muss - aber auch koennte
Function specialMenuOnClick:Int(sender:TMenu)
'jemand hat die Funktion nicht korrekt aufgerufen
if not sender then return False
print sender.name + " wurde angeklickt - und das ist sonderbar"
End Function
local menu:TMenu = new TMenu
menu.onClickFunc = menuOnclick
local menu2:TMenu = new TMenu
menu.onClickFunc = menuOnclick
local menu3:TMenu = new TMenu
menu.onClickFunc = specialMenuOnClick
...
Natuerlich kann man auch ein "Standardverhalten" (eine DefaultOnClick-Funktion) festlegen und nur bei Bedarf eine andere "drueberlegen". In obigem Fall: "menuOnClick" wuerde "DefaultOnClick" heissen und in TMenu gespeichert werden. Das die Field-Zeile dann "Field onClickFunc:int(sender:TMenu) = TMenu.DefaultOnClick".
Wenn Du unterscheiden willst, mit was geklickt worden ist: entweder neue Funktionen "onRightclick" oder ein weiterer Parameter in die Klickfunktionen um uebergeben mit welcher Maustaste das geschah (oder an welcher Koordinate).
b)
Du kannst das Eventsystem nutzen. Statt dann "onClick" aufzrufen, feuerst du einen selbstbenannten Event ab - mit dem Objekt als "Sender", der "Payload" (also "Anhaengsel") wer geklickt hat, mit was er geklickt hat, usw. .
Bis dahin passiert dann erstmal nix - praktisch wenn nicht jedes Element auf Mausklicks reagiert.
Bist Du an einem Klick auf ElementXY interessiert, erstellst Du dir einen "EventListener" (siehe Demoapp - die macht das fuer die GUIObjektklicks) der nur auf das gewuenschte Objekt reagiert (oder nur auf den "Typ" oder ...). Dieser Listener fuehrt im einfachsten Fall eine uebermittelte Funktion aus sobald ein solches Event abgefeuert wird. Diese Funktionen sehen dann aehnlich aus wie die Funktionen aus Variante A) - nur mit anderen Parametern (das Eventobjekt wird uebergeben).
So was ist der Vorteil?
a) Kein zusaetzlicher Overhead eines Eventsystems, es ist sofort klar, was wie passiert
b) Das Eventsystem kapselt Dinge voneinander ab. Ein "Listener" muss den "Sender" nicht kennen. Es werden nur relevante Informationen uebermittelt. Ein Objekt A kann also im Quellcode hinzukommen ohne dass wir Objekt B dies mitteilen muessten. Vorallem bei "modularem Code" ist dies ein enormer Vorteil - man muss nicht alles in eine einzige grosse Datei schreiben - oder in jeder Datei die anderen "referenzieren". Das ganze nennt sich "Abhaengigkeit".
- ObjektA.bmx - importiert ObjektB.bmx
- ObjektB.bmx will aber Informationen von ObjektA.bmx - muesste sie also auch importieren
-> nicht moeglich.
Also kann man nicht "importieren" (Import = Code der autark ist, "Include" ist eher so, dass diese Zeile dann mit dem Inhalt der Datei ersetzt wird) sondern muss "include" nutzen. Man koennte also gleich eine Datei schreiben: ObjektA_und_ObjektB.bmx
Doof, wenn man in einem Projekt dann mal nur ObjektA braucht ... ohne den Kram von ObjektB.
Durch die Anonymisierung mittels des Eventsystems loest man dieses Problem. Hat aber dafuer einen Mehraufwand - man muss immer "Casten" ob der Sender (gespeichert als "Object") vom richtigen Typ ist.
Ein weiterer Vorteil ist aber: es kann bequem mehrere Listener geben, die sich nicht kennen muessen. Im Spiel waere das bspweise sowas:
- ein Listener interessiert sich fuer Menuklicks um den naechsten Bildschirm zu laden (entsprechend geklicktem Menu)
- ein anderer Listener interessiert sich fuer Menuklicks im allgemeinen - um einen Klicksound abzuspielen
Der Teil im Code, der sich um Bildschirme kuemmert, muss also nix vom Soundsystem wissen - ob das nun da ist oder nicht, scheissegal.
Auch koennte ein MenuB einen Listener registrieren, der darauf reagiert, wenn auf MenuA geklickt wird.
Dennoch: nicht fuer alles Events einsetzen: denn auch hier gilt: es gibt Tradeoffs - vorallem fuer einfache Dinge (wo man weiss: das "interessiert" nur das eine Objekt/den einen Typ) kann man das klassische Prinzip mit der verknuepfbaren Funktion nutzen (wie in Beispiel a) ).
Nochmals kurz zur "Magie" - in TVTower funktioniert auch nix "komplett automatisch" ... sondern irgendwo wird ein Stein angeschubst - der andere dann umkippt. Ein "TPlayer.UpdateAll" ruft die Methode "Update" fuer jeden Spieler auf. Diese Methode ruft wiederum die Updates fuer andere Objekte von "TPlayer" auf... diese rufen dann wieder ...
Im Quellcode sieht dass dann natuerlich nach wenig aus - eine Zeile mit einem Update ... aber nach hinten kommt der Rattenschwanz.
Mach dir wegen ein paar Updates pro "UpdateWorld" keine Gedanken. Wenn es dann doch mal zuviel wird (viele Ueberpruefungen obwohl gar nix gemacht werden muss) - dann wechsel zu einem eventbasierten System, denn dass macht ja - wenn nix passiert - nur die Ueberpruefung ob ein Event vorliegt
Die groesste Last in einem Spiel erzeugt der "Zeichnen"-Teil ... Beides limitieren wir ja aber - "Logik" 30x pro Sekunde, "Rendern" bis zu 60x (Logik hat Vorrang - damit man auch jeden Klick mitbekommt - sehen muss man ihn ja nicht). Es gibt natuerlich Engines die 200 oder mehr "Updates" pro Sekunde durchfuehren. Fuer unser Anliegen reichen aber theoretisch sogar 10 oder 5.
Denn: es gibt noch das sogenannte "Tweening" - wenn man fuer ein Objekt die alte und die neue Position speichert, kann man mittels Tweening selber festlegen wo das Objekt derzeit sein muesste. Dazu gibt es einen Faktor der ansagt, wie weit (in Prozent) wir vom letzen Update entfernt sind (0.5 waere also: das Zeichnen findet genau zwischen zwei UpdateWorld() statt). Sowas ist aber nur interessant, wenn wir Animationen haben (bei TVTower der "Mond").
Will man auf Tweening verzichten und hat Animationen -- einfach die Updaterate hoeher als die Renderrate ansetzen (dann wird ja pro Rendern mindestens einmal Update ausgefuehrt).
Dazu aber erst bei Bedarf "mehr".
So...hoffe der Kopf qualmt ein wenig - aber nicht zu viel
bye
Ron
Offline
Zwei kleine Fragen noch:
1.)Habe ich das richtig verstanden, das jetzt 60 mal in der Sekunde der gesamte Bildschirm/Fenster neu gezeichnet wird? (RenderWorld ausgeführt wird)
2.)Eine Boolen variable gibt's in BMax nicht? nimmt man ein Int?
Beitrag geändert von sushiTV (06.04.2014 11:42)
das Leben ist ein scheiß Spiel, hat aber ne geile Grafik
Offline
Bool gibt es bei BlitzMax nicht
False ist aequivalent zu: Byte 0, String "", Int 0, einem Null-Objekt
Null bei "nicht objekten" ist aequivalent zu "False" (0, "")
also:
local myobject:MyObject 'per se "null"
if myobject then dosomething
@neugezeichnet
ja hast Du richtig verstanden. Es gibt hier keine "DirtyRects"-Methode wie in den 90ern - bei dem der alte Bildschirm immer nur "uebermalt" werden musste. Da der Grafikspeicher nicht kontrolliert wird, muss man immer artig neuzeichnen.
Wenn du einen bildschirmfuellenden Hintergrund nutzt, kannst Du das "Cls" weglassen.
bye
Ron
Offline
Sooo,
Ich habe nun in TMenu weitere Methoden eingefügt
(dadurch brauche ich nun status und Ustatus nicht mehr)
Funktioniert auch fast schon .... aber das ganze Verschieben des Menüs ...
da muss ich mir etwas einfallen lassen das ganz anders zu lößen
(vielleicht ja durch die onClick sache etc. ...)
Das Weinbergmenü schließt sich ab und an direkt wieder wenn es angeklickt wird,
bestimmt weil das auch von der Programmlogik nicht ganz sauber ist.
Mal sehen ob der Code überhaupt hier rein passt - das nicht das Forum sprengt
gruß
sushi
'
'
'My first BlitzMax program
'Title: Winzer Projekt
'Datum: 2014.04.05 - 06
'Autor: ~s~
'
'Using Dig Framework https://github.com/GWRon/Dig
'Using Dig Framework http://www.gamezworld.de/phpforum/viewtopic.php?id=13394
'
'
'
Import "base.util.helper.bmx" 'hilfsfunktionen
Import "base.util.event.bmx" 'mit neuem DigUpdate nicht noetig, hab das beim deltatimer vergessen :D
Import "base.util.deltatimer.bmx"
'Include BRL.PNGLoader
Graphics 800, 600, 0, 0
Global exitApp:int = FALSE 'spiel beenden?
Global bild_bg:TImage
Global bild_bgp1:TImage
'Global Ustatus:Int = - 1
Global MenuStatus:Int = 1
Global aktivesMenu:TMenu
Global hoverMenu:TMenu
Global Hauptmenu:TMenu[]
Global Stadtmenu:TMenu[]
Global Weinbergmenu:TMenu[]
Global Dummy:String
Type TPlayer
Field Home:String
Field Konto:Float
Field Keller:TKeller
End Type
Type TKeller
Field Flaschen:Int
Field Lagerplatz:Int
End Type
Type TMenu
Field x:Int
Field y:Int
Field b:Int
Field h:Int
Field name:String
' Field status:Int
Method Draw()
If Not Self.IsActive() And Not Self.IsHover() Then
SetColor(232, 222, 191)
ElseIf Self.IsHover() And Not Self.IsActive() Then
SetColor(223, 144, 26)
ElseIf Self.IsActive() Then
SetColor(243, 202, 11)
EndIf
DrawText(name, x, y)
'Farbe wieder zuruecksetzen - nicht vergessen :D
SetColor(255, 255, 255)
End Method
Method Update:Int()
'standardmaessig ausschalten
'status = 0
'wenn die maus nicht drin ist - überprüfen ob Objekt hoverMenu ist
If Not THelper.MouseIn(x, y, b, h) Then
If hoverMenu = Self Then
hoverMenu = Null
Return False
Else
Return False
EndIf
EndIf
'du kannst mit "self." explizit hinweisen, was du meinst
'".status" (also nur punkt) wuerde explizit eine "global" variable
'ansprechen
Self.SetHover()
If MOUSEMANAGER.isDown(1) Then
Self.SetActive()
EndIf
End Method
Method SetActive()
aktivesMenu = Self
MenuStatus = 1
End Method
Method SetHover()
hoverMenu = Self
MenuStatus = 1
End Method
Method IsActive:Int()
Return (Self = aktivesMenu)
End Method
Method IsHover:Int()
Return (Self = hoverMenu)
End Method
End Type
Function MenuInit()
Local x, y, h, b:Int
x = 75
y = 180
h = 19
b = 120
For i = 0 To 5
Local NewMenu:TMenu
NewMenu = New TMenu
NewMenu.x = x
NewMenu.y = y + (i * h)
NewMenu.b = b
NewMenu.h = h
' NewMenu.status = 0
Hauptmenu:+ [NewMenu]
Next
h = 19
x = 75 + 10
y = 180 + h
b = 120
For i = 0 To 6
Local NewMenu1:TMenu
NewMenu1 = New TMenu
NewMenu1.x = x
NewMenu1.y = y + (i * h)
NewMenu1.b = b
NewMenu1.h = h
' NewMenu1.status = 0
Stadtmenu:+ [NewMenu1]
Next
h = 19
x = 75 + 10
y = 180 + h * 2
b = 120
For i = 0 To 5
Local NewMenu2:TMenu
NewMenu2 = New TMenu
NewMenu2.x = x
NewMenu2.y = y + (i * h)
NewMenu2.b = b
NewMenu2.h = h
' NewMenu2.status = 0
Weinbergmenu:+ [NewMenu2]
Next
Hauptmenu[0].name = "Zur Stadt"
Hauptmenu[1].name = "Weinberg"
Hauptmenu[2].name = "Schuppen"
Hauptmenu[3].name = "Lagerhaus"
Hauptmenu[4].name = "Kellerei"
Hauptmenu[5].name = "Wohnhaus"
Stadtmenu[0].name = "Sabotage"
Stadtmenu[1].name = "Bank"
Stadtmenu[2].name = "Arbeitsamt"
Stadtmenu[3].name = "Grosshandel"
Stadtmenu[4].name = "Einzelhandel"
Stadtmenu[5].name = "Rathaus/Amt"
Stadtmenu[6].name = "Werbebuero"
Weinbergmenu[0].name = "Weinbergmenu 1"
Weinbergmenu[1].name = "Weinbergmenu 2"
Weinbergmenu[2].name = "Weinbergmenu 3"
Weinbergmenu[3].name = "Weinbergmenu 4"
Weinbergmenu[4].name = "Weinbergmenu 5"
Weinbergmenu[5].name = "Weinbergmenu 6"
End Function
Function MenuDraw()
Local x:Int = 75, y:Int = 180, h:Int = 19, b:Int = 120
If Not MenuStatus = 1 Then Return False
MenuStatus = - 1
Print "Menue Zeichnen"
SetColor(129, 8, 39)
DrawRect(x, y, 130, 500)
'Farbe wieder zuruecksetzen - nicht vergessen :D
SetColor(255, 255, 255)
'Hauptmenu in jedem Fall Zeichnen,
'da es keine Bedingung gibt für die nicht mit IF abgeglichenen Menüs
'das ist alles müll :)
For Local i:Int = 0 To Hauptmenu.length - 1
Hauptmenu[i].x = x
Hauptmenu[i].y = y + (i * h)
Hauptmenu[i].Draw
Next
If aktivesMenu = Hauptmenu[0] Then
SetColor(129, 8, 39)
DrawRect(x, y, 130, 500)
SetColor(255, 255, 255)
x = 75
y = 180
Hauptmenu[0].Draw
For Local k:Int = 0 To Stadtmenu.length - 1
y = y + h
'Stadtmenu Koordinaten müssen nicht geändert werden
Stadtmenu[k].Draw()
Next
For Local a:Int = 1 To Hauptmenu.length - 1
y = y + h
Hauptmenu[a].y = y
Hauptmenu[a].Draw
Next
EndIf
If aktivesMenu = Hauptmenu[1] Then
SetColor(129, 8, 39)
DrawRect(x, y, 130, 500)
SetColor(255, 255, 255)
x = 75
y = 180
Hauptmenu[0].Draw
y = y + h
Hauptmenu[1].y = y
Hauptmenu[1].Draw
For Local k1:Int = 0 To Weinbergmenu.length - 1
y = y + h
'Koordinaten müssen nicht geändert werden
Weinbergmenu[k1].Draw()
Next
For Local a1:Int = 2 To Hauptmenu.length - 1
y = y + h
Hauptmenu[a1].y = y
Hauptmenu[a1].Draw
Next
EndIf
End Function
'spiellogik aktualisieren
Function UpdateWorld:int()
'refresh mouse/keyboard
MouseManager.ChangeStatus()
KeyManager.ChangeStatus()
For Local menu:TMenu = EachIn Hauptmenu
menu.Update()
Next
If aktivesMenu = Hauptmenu[0] Then
For Local menu2:TMenu = EachIn Stadtmenu
menu2.Update()
Next
EndIf
If aktivesMenu = Hauptmenu[1] Then
For Local menu3:TMenu = EachIn Weinbergmenu
menu3.Update()
Next
EndIf
'spiel beendbar mit ESC
If KeyManager.IsHit(KEY_ESCAPE) then exitApp = TRUE
End Function
Function RenderWorld:Int()
DrawImage bild_bg, 0, 0
DrawImage bild_bgp1, 15, 15
SetColor(232, 222, 191)
DrawText (Dummy, 280, 47)
'Farbe wieder zuruecksetzen - nicht vergessen :D
SetColor(255, 255, 255)
MenuDraw()
Flip 0 'alles gezeichnete "an die grafikkarte schicken"
End Function
'wir sagen dem deltatimer, was die update- und renderfunktionen sind
GetDeltaTimer()._funcUpdate = UpdateWorld
GetDeltaTimer()._funcRender = RenderWorld
'und dass wir 30 Logikupdates und 60 Renders pro Sekunde wollen
GetDeltaTimer().Init(30, 60)
'Sachen initialisieren
MenuInit()
Dummy = "Datum: Jan 1980 Ort: Weingut Sachsen Konto: 100.000"
bild_bg = LoadImage ("bg_01.png")
bild_bgp1 = LoadImage ("bgp_weingut.png")
'game loop
Repeat
'run the deltatimer's loop
'which runs the hooked update/render function
GetDeltaTimer().loop()
'falls du mit events arbeitest - muessen die natuerlich hier
'abgearbeitet werden
'EventManager.update()
Until AppTerminate() Or exitApp
End
Nachtrag:
Ich bin ja auch bissel doof .... eigentlich kann ich doch das ganze "Verschieben" in der MenuDraw() auch einfacher machen ... dachte in eine Schleife mit Unterschleifen aber das wird dann auch wieder doof
Zeichne 1. Menüpunkt
Prüfe ob erstes Menü aktiv dann Zeichne
Untermenü und erhöhe y koordinate ansonsten wird ja nix gemacht
Setze y Koordinate Menü 2 und Zeichne es
Prüfe ob zweites Menü aktiv dann Zeichne
Untermenü und erhöhe y koordinate ansonsten wird ja nix gemacht
Setze y Koordinate Menü 3 und Zeichne es
usw. ... zwar nicht elegant aber müsste funzen
ist aber auch keine lößung
Beitrag geändert von sushiTV (06.04.2014 14:40)
das Leben ist ein scheiß Spiel, hat aber ne geile Grafik
Offline
Sooo, nach etwas weiterer Spielerei, werde ich versuchen das mit dem von Ron beschriebener onClick Funktion umzusetzen
~s~
das Leben ist ein scheiß Spiel, hat aber ne geile Grafik
Offline
Offline
HeeHee
naja, ich habe jetzt onClick ereignis bei mir mit in's TMenu gepackt da ist aber irgenwo was falsch, es funktioniert zwar, macht aber nicht das was ich wollte
hihi
~s~
das Leben ist ein scheiß Spiel, hat aber ne geile Grafik
Offline
Folgender Code ist noch nicht optimal - Man kann das wie immer auf verschiedensten Wegen loesen.
Ich persoenlich haette alle linken Menuepunkte einer "Menugruppe" zugeordnet - und die wuerde sich dann entsprechend um die Positionierung kuemmern. Denn die "Hauptmenues" sind ja eigentlich auch nur eine Gruppe voneinander abhaengiger Menuepunkte. Sie getrennt zu positionieren ist einfach nur "bloed". Das macht man nur, wenn man einen Menupunkt auch woanders hinstecken woellte.
Wie dann aber reagieren,wenn man den Menuepunkt mittendrin platziert - woher wissen die anderen, dass sie "drumherumfliessen" muessen?
SuperStrict 'fordert ein wenig mehr "Definition" (Variablentypen festlegen usw)
Import "base.util.helper.bmx" 'hilfsfunktionen
Import "base.util.event.bmx" 'mit neuem DigUpdate nicht noetig, hab das beim deltatimer vergessen :D
Import "base.util.deltatimer.bmx"
'Include BRL.PNGLoader
Graphics 800, 600, 0, 0
Global exitApp:int = FALSE 'spiel beenden?
Global bild_bg:TImage
Global bild_bgp1:TImage
Global Hauptmenu:TMenu[]
Global Dummy:String
Type TPlayer
Field Home:String
Field Konto:Float
Field Keller:TKeller
End Type
Type TKeller
Field Flaschen:Int
Field Lagerplatz:Int
End Type
Type TMenu
Field x:Int
Field y:Int 'das y bei dem das menu DERZEIT ist
Field initialY:int 'hier ist es, wenn es nicht verschoben waere
Field b:Int
Field h:Int
'ist die x,y relativ zum Elternelement?
Field absolutePositioned:Int = True
Field name:String
Field parentMenu:TMenu
Field subMenus:TList = CreateList()
Global activeMenu:TMenu
Global hoveredMenu:TMenu
Method Init:TMenu(name:String, x:int=0,y:int=0,b:int=120,h:int=19)
self.name = name
self.x = x
self.y = y
self.initialY = y
self.b = b
self.h = 19
return self
End Method
'mittels den Offsets koennen wir Sachen von "aussen" her verschieben
'das erlaubt, dass die Kinderelemente "relativ" zu ihren Eltern
'positioniert sein koennen
Method Draw(offsetX:int=0, offsetY:int=0)
If IsActive() Then
SetColor(243, 202, 11)
elseIf IsHovered() Then
SetColor(223, 144, 26)
Else
SetColor(232, 222, 191)
EndIf
DrawText(name, x + offsetX, y + offsetY)
'Farbe wieder zuruecksetzen - nicht vergessen :D
SetColor(255, 255, 255)
'Submenues zeichnen - offset nicht vergessen
'Submenues interessieren uns nur, wen unser Menupunkt oder
'eines der Submenues aktiviert ist
if HasActiveSubMenu()
For local m:TMenu = eachin subMenus
m.Draw(offsetX, offsetY)
Next
Endif
End Method
'Anstatt nur "x" abzurufen, nehmen wir einen Getter um den Wert
'manipulieren zu koennen
Method GetX:int()
'hat man ein Elternelement dann addiert man die eigene
'Koordinate zu dem des Elternmenues
if not absolutePositioned and parentMenu then return parentMenu.GetX() + x
return x
End Method
Method GetY:int()
if not absolutePositioned and parentMenu then return parentMenu.GetY() + y
return y
End Method
'ermittelt die Hoehe eines Menues - inklusive der Hoehe durch Submenues
Method GetHeight:int()
local height:int = 0
'falls Submenues vorhanden sind und das menu aktiv ist
if HasActiveSubMenu()
For local m:TMenu = eachin subMenus
'rekursiv alle Untermenues abklappern
height :+ m.GetHeight()
Next
Endif
height :+ h
return height
End Method
Method Update:Int()
if THelper.MouseIn(GetX(), GetY(), b, h)
SetHovered()
If MOUSEMANAGER.isDown(1)
SetActive(True)
'wir haben den Mausklick "fuer uns" beansprucht: also Button resetten
'damit kein "folgender" code (in dem gleichen Loop) nochmal
'den gedrueckten Button vorfindet
MOUSEMANAGER.ResetKey(1)
EndIf
EndIf
'submenues interessieren uns nur, wen unser Menupunkt oder
'eines der Submenues aktiviert ist
if HasActiveSubMenu()
local displaceY:int = self.h
For local m:TMenu = eachin subMenus
if not m.absolutePositioned
'wir setzen das y des menues : original y + verschiebung
m.y = y + m.initialY + displaceY
'einruecken
m.x = x + 10
displaceY :+ m.GetHeight()
endif
m.Update()
Next
Endif
End Method
Method AddSubmenu(menu:TMenu)
'festlegen dass der Menuepunkt "relativ" angeordnet ist
menu.SetAbsolutePositioned(False)
subMenus.AddLast(menu)
End Method
'hier mal eine Methode die das Objekt zurueckgibt, erlaubt
'"Chaining" ( new TMenu.SetAbsolutePositioned().Init() )
Method SetAbsolutePositioned:TMenu(bool:Int=True)
absolutePositioned = bool
return self
End Method
Method SetActive(bool:int=True)
If bool = True
activeMenu = Self
Else
'nur resetten wenn dieses Menu das aktive ist
if activeMenu = Self then activeMenu = null
Endif
End Method
Method SetHovered(bool:int=True)
If bool = True
hoveredMenu = Self
Else
'nur resetten wenn dieses Menu das gehoverte ist
if hoveredMenu = Self then hoveredMenu = null
Endif
End Method
Method HasActiveSubMenu:Int()
'Rekursive Funktion - ruft fuer jedes Kinderelement die gleiche
'Funktion nochmal auf - hat ein Submenu ein Submenu wird dort
'geschaut ob dies ein Submenu hat was aktiv ist ... und so weiter
For local m:TMenu = eachin subMenus
if m.HasActiveSubMenu() then return True
Next
return IsActive()
End Method
Method IsActive:Int()
Return (Self = activeMenu)
End Method
Method IsHovered:Int()
Return (Self = hoveredMenu)
End Method
End Type
Function MenuInit()
Local x:int=75, y:int=180, h:int=19, b:int=120
'5 hauptmenues anlegen
HauptMenu:+ [new TMenu.Init("Zur Stadt", x, y+0*h)]
HauptMenu:+ [new TMenu.Init("Weinberg", x, y+1*h)]
HauptMenu:+ [new TMenu.Init("Schuppen", x, y+2*h)]
HauptMenu:+ [new TMenu.Init("Lagerhaus", x, y+3*h)]
HauptMenu:+ [new TMenu.Init("Kellerei", x, y+4*h)]
HauptMenu:+ [new TMenu.Init("Wohnhaus", x, y+5*h)]
'dem "Zur Stadt"-Menu Unterpunkte hinzufuegen
HauptMenu[0].AddSubmenu( new TMenu.Init("Sabotage") )
HauptMenu[0].AddSubmenu( new TMenu.Init("Bank") )
HauptMenu[0].AddSubmenu( new TMenu.Init("Arbeitsamt") )
HauptMenu[0].AddSubmenu( new TMenu.Init("Grosshandel") )
HauptMenu[0].AddSubmenu( new TMenu.Init("Einzelhandel") )
HauptMenu[0].AddSubmenu( new TMenu.Init("Rathaus/Amt") )
HauptMenu[0].AddSubmenu( new TMenu.Init("Werbebuero") )
'dem "Weinberg"-Menu Unterpunkte hinzufuegen
HauptMenu[1].AddSubmenu( new TMenu.Init("Weinbergmenu 1") )
HauptMenu[1].AddSubmenu( new TMenu.Init("Weinbergmenu 2") )
HauptMenu[1].AddSubmenu( new TMenu.Init("Weinbergmenu 3") )
HauptMenu[1].AddSubmenu( new TMenu.Init("Weinbergmenu 4") )
HauptMenu[1].AddSubmenu( new TMenu.Init("Weinbergmenu 5") )
HauptMenu[1].AddSubmenu( new TMenu.Init("Weinbergmenu 6") )
End Function
Function MenuDraw()
SetColor(129, 8, 39)
DrawRect(75, 180, 130, 500)
'Farbe wieder zuruecksetzen - nicht vergessen :D
SetColor(255, 255, 255)
'alle Hauptmenues zeichnen
For Local m:TMenu = eachin Hauptmenu
m.Draw()
Next
End Function
'spiellogik aktualisieren
Function UpdateWorld:int()
'refresh mouse/keyboard
MouseManager.ChangeStatus()
KeyManager.ChangeStatus()
'alle Hauptmenues (und ihre Submenues) aktualisieren
'Per se ist am anfang kein Menu "gehovert" - der Hoverstatus
'wird automatisch wiederhergestellt wenn die Maus noch drueber
'ist
TMenu.hoveredMenu = null
'da wir wollen, dass alle menues "relativ" zueinander sind
'verschieben wir alle dem ersten nachfolgenden Menues
local displaceY:int = 0
For Local menu:TMenu = EachIn Hauptmenu
menu.Update()
'wir setzen das y des menues : original y + verschiebung
menu.y = menu.initialY + displaceY
'neuen displace wert fuer das darauffolgende Menu festlegen
'dabei beachten: wir wollen nur die Hoehe der "Kinderelemente"
'also ziehen wir die eigene Hoehe ab.
'Alternativ: Methode "GetSubMenuHeight" - was nur submenu
'durchrackern muesste
displaceY :+ menu.GetHeight() - menu.h
Next
'spiel beendbar mit ESC
If KeyManager.IsHit(KEY_ESCAPE) then exitApp = TRUE
End Function
Function RenderWorld:Int()
DrawImage bild_bg, 0, 0
DrawImage bild_bgp1, 15, 15
SetColor(232, 222, 191)
DrawText (Dummy, 280, 47)
'Farbe wieder zuruecksetzen - nicht vergessen :D
SetColor(255, 255, 255)
MenuDraw()
Flip 0 'alles gezeichnete "an die grafikkarte schicken"
End Function
'wir sagen dem deltatimer, was die update- und renderfunktionen sind
GetDeltaTimer()._funcUpdate = UpdateWorld
GetDeltaTimer()._funcRender = RenderWorld
'und dass wir 30 Logikupdates und 60 Renders pro Sekunde wollen
GetDeltaTimer().Init(30, 60)
'Sachen initialisieren
MenuInit()
Dummy = "Datum: Jan 1980 Ort: Weingut Sachsen Konto: 100.000"
bild_bg = LoadImage ("bg_01.png")
bild_bgp1 = LoadImage ("bgp_weingut.png")
'game loop
Repeat
'run the deltatimer's loop
'which runs the hooked update/render function
GetDeltaTimer().loop()
'falls du mit events arbeitest - muessen die natuerlich hier
'abgearbeitet werden
'EventManager.update()
Until AppTerminate() Or exitApp
End
bye
Ron
Offline
Alles was irgendwie in Abhaengigkeit zu anderen Dingen steht ist dann schon weniger trivial zu realisieren.
Man muss ja immer wissen wo man gerade ist - was abhaengig von "oben drueber" ist. Macht die Sache nicht einfacher.
Ja, man koennte wieder mit Variablen arbeiten die den aktuellen Modus kennen, die aktuelle Position usw. - aber diese mangelnde Dynamik hat dann halt irgendwann auch Probleme (wenn man Sachen erweitern will).
bye
Ron
Offline
So ... hab dir ja gesagt, ich persoenlich wuerde das etwas anders gestalten.
Ich hatte vorher den Typ TMenu etwas erweitert... so mit "Field Root:int = False" usw. Dann innerhalb der Zeichnen/Update-Funktionen immer ueberprueft, ob es das Wurzelelement ist...
Aber dann habe ich mir gedacht: Du willst ja "oop" lernen - und in folgendem Code "vererben" wir ein wenig ... TRootMenu ueberschreibt alle relevanten Funktionen damit bspweise nicht geschaut wird, ob die Maus "drueber" ist.
Man kann sowas dann wieder mit "Field ignoreMouse:int=False" und entsprechender Abfrage gestalten - dass ist dann Geschmackssache.
Noch ein weiterer Hinweis:
Ich habe im Typ TMenu das Feld "depth:int" hinterlegt - damit kann das Menu schnell wissen, wie tief verschachtelt es ist. Natuerlich koennte man auch eine "GetDepth"-Methode schreiben, die immer wieder (rekursiv) vom Elternelement "GetDepth" aufrufen wuerde - und dabei einen Zaehler hochzaehlt.
Method GetDepth:int()
local depth:int = 0
if parentMenu then depth :+ 1 + GetDepth()
return depth
End Method
Du siehst: hat man kein Elternmenu: bleibt depth = 0, hat es ein parentMenu, wird 1 addiert und dann noch das Ergebnis davon, welche Tiefe das parentMenu bereits besitzt. Dies nennt sich dann wie oben schon angedeutet "Rekursion".
Wenn du diese Methode einbindest, kannst du auf das "Field depth:int" verzichten und "depth" mit "GetDepth()" ersetzen.
Der bereits beschriebene Punkt "TradeOff" ist hier: die Sache wird dynamisch und weniger fehleranfaellig - dafuer muss beim "auslesen" mehr getan werden als nur eine Variable zurueckzugeben.
Bei wirklich komplexen Prozessen wuerde man dann mit "caches" arbeiten (bspweise wenn man dynamisch Bilder erstellt). Dann muss man allerdings darauf achten, wann der Cache geloescht werden muss (bei Bildern bspweise, wenn sich der darauf abzubildende Inhalt aendert). Aber dies ist was fuer spaeter
So ... hier nun mein "verbesserter" Code:
SuperStrict 'fordert ein wenig mehr "Definition" (Variablentypen festlegen usw)
Import "base.util.helper.bmx" 'hilfsfunktionen
Import "base.util.event.bmx" 'mit neuem DigUpdate nicht noetig, hab das beim deltatimer vergessen :D
Import "base.util.deltatimer.bmx"
'Include BRL.PNGLoader
Graphics 800, 600, 0, 0
Global exitApp:int = FALSE 'spiel beenden?
Global bild_bg:TImage
Global bild_bgp1:TImage
Global linkesMenu:TMenu
Global Dummy:String
Type TPlayer
Field Home:String
Field Konto:Float
Field Keller:TKeller
End Type
Type TKeller
Field Flaschen:Int
Field Lagerplatz:Int
End Type
Type TMenu
'derzeitige Position des Menues
Field x:Int, y:int
'Dimension des Menues
Field b:Int, h:Int
'wie tief verschachtelt?
Field depth:int = 0
'submenues immer anzeigen?
Field alwaysDisplaySubMenus:int = FALSE
Field name:String = ""
Field parentMenu:TMenu
Field subMenus:TList = CreateList()
Global activeMenu:TMenu
Global hoveredMenu:TMenu
Method Init:TMenu(name:String, x:int=0,y:int=0,b:int=120,h:int=19)
self.name = name
self.x = x
self.y = y
self.b = b
self.h = 19
return self
End Method
Method DrawSubmenu()
'Submenues interessieren uns nur, wen unser Menupunkt oder
'eines der Submenues aktiviert ist
if HasActiveSubMenu() or alwaysDisplaySubMenus
For local m:TMenu = eachin subMenus
m.Draw()
Next
Endif
End Method
Method Draw()
If IsActive() Then
SetColor(243, 202, 11)
ElseIf IsHovered() Then
SetColor(223, 144, 26)
Else
SetColor(232, 222, 191)
EndIf
DrawText(name, GetX(), GetY())
'Farbe wieder zuruecksetzen - nicht vergessen :D
SetColor(255, 255, 255)
'Submenues zeichnen
DrawSubmenu()
End Method
'Anstatt nur "x" abzurufen, nehmen wir einen Getter um den Wert
'manipulieren zu koennen
Method GetX:int()
local result:int = x
'hat man ein Elternelement dann addiert man die eigene
'Koordinate zu dem des Elternmenues
if parentMenu then result :+ parentMenu.GetX()
'fuer jede "Verschachtelung" gehen wir 10 pixel nach rechts
result :+ depth*5
return result
End Method
Method GetY:int()
local result:int = y
If parentMenu
result :+ parentMenu.GetY()
result :+ parentMenu.GetHeight()
'fuer alle submenueintraege vor mir: deren hoehe hinzufuegen
For local m:TMenu = eachin parentMenu.subMenus
'mich selber gefunden - raus aus der Schleife
if m = self then exit
'andernfalls: hoehe addieren
result :+ m.GetHeight()
result :+ m.GetSubmenuHeight()
Next
EndIf
return result
End Method
'ermittelt die Hoehe eines Menues - ohne Submenues
Method GetHeight:int()
return h
End Method
'liefert die Hoehe der Untermenupunkte zurueck
Method GetSubmenuHeight:int()
local height:int = 0
if HasActiveSubMenu() or alwaysDisplaySubMenus
For local m:TMenu = eachin subMenus
'rekursiv alle Untermenues abklappern
height :+ m.GetHeight()
height :+ m.GetSubmenuHeight()
Next
Endif
return height
End Method
Method Update:Int()
if THelper.MouseIn(GetX(), GetY(), b, h)
SetHovered()
If MOUSEMANAGER.isDown(1)
SetActive(True)
'wir haben den Mausklick "fuer uns" beansprucht: also Button resetten
'damit kein "folgender" code (in dem gleichen Loop) nochmal
'den gedrueckten Button vorfindet
MOUSEMANAGER.ResetKey(1)
EndIf
EndIf
'submenues aktualisieren
UpdateSubmenu()
End Method
Method UpdateSubmenu:Int()
if HasActiveSubMenu() or alwaysDisplaySubMenus
local menuDisplaceY:int = 0
For local m:TMenu = eachin subMenus
m.Update()
Next
Endif
End Method
Method AddSubmenu(menu:TMenu)
'menu ist eins tiefer als das Elternelement
menu.depth = self.depth + 1
'dem menu mitteilen, zu wem sie nun gehoeren
menu.parentMenu = self
subMenus.AddLast(menu)
End Method
Method SetActive(bool:int=True)
If bool = True
activeMenu = Self
Else
'nur resetten wenn dieses Menu das aktive ist
if activeMenu = Self then activeMenu = null
Endif
End Method
Method SetHovered(bool:int=True)
If bool = True
hoveredMenu = Self
Else
'nur resetten wenn dieses Menu das gehoverte ist
if hoveredMenu = Self then hoveredMenu = null
Endif
End Method
Method HasActiveSubMenu:Int()
'Rekursive Funktion - ruft fuer jedes Kinderelement die gleiche
'Funktion nochmal auf - hat ein Submenu ein Submenu wird dort
'geschaut ob dies ein Submenu hat was aktiv ist ... und so weiter
For local m:TMenu = eachin subMenus
if m.HasActiveSubMenu() then return True
Next
return IsActive()
End Method
Method IsActive:Int()
Return (Self = activeMenu)
End Method
Method IsHovered:Int()
Return (Self = hoveredMenu)
End Method
End Type
'Der "Start" eines Menus
Type TRootMenu extends TMenu
'ueberschreiben von TMenu.Init - Achtung: anderen Typ zurueckgeben
Method Init:TRootMenu(name:String, x:int=0,y:int=0,b:int=120,h:int=19)
'Super - ruft den Elterntyp auf - hier also "TMenu"
'-> Init von "TMenu" wird aufgerufen
Super.Init(name, x, y, b, h)
'unterpunkte immer anzeigen
alwaysDisplaySubMenus = True
return self
End Method
'ueberschreiben da der root immer eine tiefe von 0 hat
Method GetX:int()
return x
End Method
'ueberschreiben von "GetHeight" - dieses rootmenu nimmt keinen Platz weg
Method GetHeight:int()
return 0
End Method
'ueberschreiben: rootMenus koennen nicht hovern
'wir aktualisieren also nur noch die Submenus
Method Update:Int()
UpdateSubmenu()
End Method
'ueberschreiben: wir zeichnen nur die submenus
Method Draw()
DrawSubmenu()
End Method
End Type
Function MenuInit()
Local x:int=75, y:int=180, h:int=19, b:int=120
linkesMenu = new TRootMenu.Init("", x, y)
'damit wir bequem diese Menues benutzen koennen...
local stadtMenu:TMenu = new TMenu.Init("Zur Stadt")
local weinbergMenu:TMenu = new TMenu.Init("Weinberg")
linkesMenu.AddSubmenu( stadtMenu )
linkesMenu.AddSubmenu( weinbergMenu )
linkesMenu.AddSubmenu( new TMenu.Init("Lagerhaus") )
linkesMenu.AddSubmenu( new TMenu.Init("Kellerei") )
linkesMenu.AddSubmenu( new TMenu.Init("Wohnhaus") )
local sabotageMenu:TMenu = new TMenu.Init("Sabotage")
stadtMenu.AddSubmenu( sabotageMenu )
stadtMenu.AddSubmenu( new TMenu.Init("Bank") )
stadtMenu.AddSubmenu( new TMenu.Init("Arbeitsamt") )
stadtMenu.AddSubmenu( new TMenu.Init("Grosshandel") )
stadtMenu.AddSubmenu( new TMenu.Init("Einzelhandel") )
stadtMenu.AddSubmenu( new TMenu.Init("Rathaus/Amt") )
stadtMenu.AddSubmenu( new TMenu.Init("Werbebuero") )
'dem sabotage menu Unterpunkte hinzufuegen
sabotageMenu.AddSubmenu( new TMenu.Init("Sabotage 1") )
sabotageMenu.AddSubmenu( new TMenu.Init("Sabotage 2") )
'dem "Weinberg"-Menu Unterpunkte hinzufuegen
weinbergMenu.AddSubmenu( new TMenu.Init("Weinbergmenu 1") )
weinbergMenu.AddSubmenu( new TMenu.Init("Weinbergmenu 2") )
weinbergMenu.AddSubmenu( new TMenu.Init("Weinbergmenu 3") )
weinbergMenu.AddSubmenu( new TMenu.Init("Weinbergmenu 4") )
weinbergMenu.AddSubmenu( new TMenu.Init("Weinbergmenu 5") )
weinbergMenu.AddSubmenu( new TMenu.Init("Weinbergmenu 6") )
End Function
Function MenuDraw()
SetColor(129, 8, 39)
DrawRect(75, 180, 130, 500)
'Farbe wieder zuruecksetzen - nicht vergessen :D
SetColor(255, 255, 255)
linkesMenu.Draw()
End Function
'spiellogik aktualisieren
Function UpdateWorld:int()
'refresh mouse/keyboard
MouseManager.ChangeStatus()
KeyManager.ChangeStatus()
'alle Hauptmenues (und ihre Submenues) aktualisieren
'Per se ist am anfang kein Menu "gehovert" - der Hoverstatus
'wird automatisch wiederhergestellt wenn die Maus noch drueber
'ist
TMenu.hoveredMenu = null
'da wir wollen, dass alle menues "relativ" zueinander sind
'verschieben wir alle dem ersten nachfolgenden Menues
linkesMenu.Update()
'spiel beendbar mit ESC
If KeyManager.IsHit(KEY_ESCAPE) then exitApp = TRUE
End Function
Function RenderWorld:Int()
DrawImage bild_bg, 0, 0
DrawImage bild_bgp1, 15, 15
SetColor(232, 222, 191)
DrawText (Dummy, 280, 47)
'Farbe wieder zuruecksetzen - nicht vergessen :D
SetColor(255, 255, 255)
MenuDraw()
Flip 0 'alles gezeichnete "an die grafikkarte schicken"
End Function
'wir sagen dem deltatimer, was die update- und renderfunktionen sind
GetDeltaTimer()._funcUpdate = UpdateWorld
GetDeltaTimer()._funcRender = RenderWorld
'und dass wir 30 Logikupdates und 60 Renders pro Sekunde wollen
GetDeltaTimer().Init(30, 60)
'Sachen initialisieren
MenuInit()
Dummy = "Datum: Jan 1980 Ort: Weingut Sachsen Konto: 100.000"
bild_bg = LoadImage ("bg_01.png")
bild_bgp1 = LoadImage ("bgp_weingut.png")
'game loop
Repeat
'run the deltatimer's loop
'which runs the hooked update/render function
GetDeltaTimer().loop()
'falls du mit events arbeitest - muessen die natuerlich hier
'abgearbeitet werden
'EventManager.update()
Until AppTerminate() Or exitApp
End
bye
Ron
Offline