Lara Croft and the Temple of Osiris ist verwirrend und verwirrt
Wednesday, 25. November 2020
Lara Croft and the Temple of Osiris ist ein kurzes Hack'n Slay mit Pistolen und Rätseln.
Da spricht erstmal nichts gegen, auch kurze Spiele im Genre können Spaß machen, wenn die Kämpfe unterhalten, es tolles Loot gibt, die Rätsel interessant sind oder die Story motiviert. The Incredible Adventures of Van Helsing beispielsweise ist so ein Hack'n Slay, das zwar kein episches Diablo 2 sein mag, aber mit eigenen Stärken trotzdem charmant und spaßig ist. Dieser Lara-Croft-Ableger fällt dagegen verblüffend stark ab.
Flotte Kämpfe, nette Rätsel
Zuerst das Gute: Die Kämpfe. Das Spiel schickt euch in mehrere kurze Dungeons, in denen einige Gegner auf euch warten. Die sind hübsch abwechslungsreich, so gibt es Skelette mit Suchprojektilen, Krokodile, die man erst umwerfen und dann mit einer Bombe erledigen muss, und übergewichtige explodierende Riesen. Die Gegner sind schnell, aber Lara Croft ist schneller, mit ihrer Ausweichrolle und den vielen Waffen sind die Kämpfe gut gelungen. Zwar sind sie meist einfach, aber ein paar der Bossgegner und die Kombinationen mit den Sprungpassagen sind dann wieder fordernder.
Auch das Leveldesign in den Dungeons ist nicht schlecht. Es gibt immer wieder kleine Schalterrätsel zu lösen, dann müssen große Bälle mit Bomben über Absperrungen geworfen werden, dabei klettert Lara von einem Vorsprung zum nächsten. Die Level sind oft erstaunlich kurz, aber dadurch sie sind selten langweilig. Und die Bossgegner kombinieren immer wieder Kämpfe mit kleinen Timing- und Rätselpassagen, sie sind allesamt gut gelungen.
Wiederspielwert?
Andererseits tut das Spiel so, als gäbe es einen Grund die Levels mehrmals zu spielen. So gibt es in ihnen Herausforderungen – ähnlich wie in Victor Vran – die dann weitere Upgrades, oder Items wie Ringe, Amulette oder sogar neue Waffen freischalten. Doch gibt das Spiel dem Spieler keinen Grund, das zu tun! Nur die Waffen wären etwas interessant, doch finden sich auch so genug und sind die Kämpfe nicht besonders schwer. Amulette und Ringe sind komplett witzlos, geben sie doch nur irrelevante Boni.
Genauso irritierend sind die vielen Schatzkisten, die überall verteilt sind. In ihnen finden sich ausschließlich (zumindest in meinem Durchlauf) Ringe und Amulette, die man ja nicht braucht. Und um die Truhen öffnen zu können, müssen gesammelte Edelsteine eingesetzt werden. Nach den meisten Dungeons kommt man in einen Schatzraum mit 20 solcher Truhen, hat aber meist nur Juwelen für eine der Truhen, und in der ist dann nichtmal etwas nützliches drin!
Lara Croft and the Temple of Osiris wirkt damit so, als hätten die Spieldesigner blind die vermeintlich motivierenden Elemente von Hack'n Slays wie eben die Truhen in ihr Spiel gepackt, dann aber nicht die Zeit gehabt, um diese Elemente drumherum ein Spiel zu basteln, das Gegenstände abwirft die in diese Truhen hereingepackt werden könnten.
Schlappe Inszenierung
Dazu kommt eine völlig hanebüchene Story. Lara und ein Konkurrent finden einen Stab, berühren ihn, daraufhin erwacht Seth und will die Menschheit versklaven. Lara, zwei ägyptische Götter und ihr Konkurrent müssen ihn aufhalten, wobei der Spieler nur einer der Figuren spielt. Okay, der Kern der Story bei Diablo mit den Seelensteinen und dem Erdenende ist auch nicht viel kreativer, aber der Unterschied ist die Inszenierung und wie die Story geschrieben ist. Was die Charaktere hier von sich geben ist einfach peinlich. Da hilft es nichts, dass einzelne der Zwischensequenzen in Spielgrafik nicht ganz schlecht aussehen.
Dass der Rest des Spiels ebenfalls nicht ganz schlecht aussieht macht es dann aber wieder ein bisschen besser. Für kurzweilige Unterhaltung kann gute Grafik nur helfen.
Spiel- oder Ignorierbar
Die ganze Sache ist dann nur etwa 5 Stunden lang. Es könnte etwas mehr sein, wenn die Zusatzlevels der DLCs enthalten wären – bei einem so kurzen Spiel nicht alles hineinzupacken ist frech. Aber das wäre kein Problem, wenn dem Spiel sein Charakter klar wäre. Es ist eine kurze und kurzweilige Unterhaltung für zwischendurch, die im Koop-Modus nochmal mehr Spaß machen soll. Hätte es sich darauf konzentriert, wäre es ein gutes Spiel geworden.
Stattdessen gibt es viel Loot, das aber komplett uninteressant ist, und nach Spielende Hinweise auf Post-Game-Inhalte, ohne dass es vorher auch nur Ansätze einer funktionieren Meta-Spielmechanik gegeben hätte. Das Komitee, das dieses Spiel zusammengestückelt hat, hat sich komplett verrannt.
Aber da die Dungeons mit ihren Kämpfen und Rätseln schon Spaß machen und das Spiel nicht zuviel Zeit kostet kann man es auch mal eben durchspielen, wenn man es umsonst oder sehr günstig bekommt. Idealerweise wohl wirklich im Koop mit mindestens einem Mitspieler, sodass das gemeinsame Bewältigen der Dungeons den Spaßfaktor erhöht. Oder man lässt es zugunsten eines besseren Spiels bleiben – viel verpassen würde man nicht.
Immutability und ein praktischer Einsatzzweck
Monday, 23. November 2020
Objekte die immutable sind können in keiner Weise modifiziert werden. Doch was bringt das?
Das Konzept
Viele Sprachen – nicht alle natürlich – unterstützen das Konzept, mal für alle Datentypen, mal nur für spezielle. Aber grundsätzlich könnte es so aussehen:
class Car immutable doors = ['door1', 'door2'] end
Wenn der Entwickler sich dann später ein entsprechendes Objekt erstellt, dann kann er das doors
-Attribut nicht ändern:
myCar = new Car(); myCar.doors.add('door3') #=> Error!
Immutability ist ein Konzept, das man besonders oft bei funktionalen Programmiersprachen finden wird. Wer sich Gedanken über Seiteneffekte macht, der wird auch Immutability bedacht haben. Es kann auch massiv dem Compiler helfen, wenn Objekte entsprechend markiert sind und er so weiß, dass diese Objekte sich nie ändern können. Programme werden so schneller oder sicherer.
Wobei, von wegen funktional, const
geht ja in eine ganz ähnliche Richtung.
Ein Anwendungsfall
Doch hilft es dem Entwickler? Das allerwichtigste ist Entwicklerzeit, -effizienz und -komfort. Immutability schadet dabei oft. Wenn ich als Entwickler ein geschütztes Attribut ändern will, habe ich wahrscheinlich einen guten Grund. Wenn ich jetzt nur des Compilers wegen ein neues angepasste Objekt erstellen und am besten noch einige Attribute kopieren muss, dann stimmen die Prioritäten nicht.
Aber jetzt lief ich in diesen Fall: Ich hatte eine Liste. Und irgendwas veränderte diese Liste. Und zwar wurde der letzte Eintrag in ihr länger. Doch nirgends in meinem Programm gab es eine solche Zuweisung, zumindest keine die eindeutig zu erkennen war.
Was wohl passiert war: Es gab ein Fold.
newList = originalList; targetMap = newList.fold({}, (prev, element) => prev..addAll(element));
Und dazu einige andere solcher Akkumulatoren, die meine Originalliste zusammenfassten (die in Wirklichkeit auch keine einfache Liste war, sondern eine Liste von Listen von Ojekten in einem Elternobjekt). Und irgendeiner davon hatte als Nebeneffekt, dass die Originalliste – denn in dieser Sprache werden meist Referenzen übergeben – ebenfalls verändert wurde.
Die Lösung: Die Liste als immutable markieren.
Die Akkumulatorfunktionen wie das fold
funktionierten immer noch. Aber anstatt die Originalliste zu verändern, erstellten sie sich automatisch ihre eigene Kopie. Die Originalliste blieb unverändert, der Bug war gefixt.
Es ist also keinesfalls so, dass Immutability nur den Code verkompliziert. Im Gegenteil, richtig eingesetzt kann es ein Programm wesentlich einfacher zu verstehen machen, dann dient das Konzept dem Entwickler. Besonders in Sprachen, die Referenzen einsetzen anstatt direkt Werte zu kopieren.
Die Radeon RX 6800 (XT) unter Linux sieht klasse aus
Wednesday, 18. November 2020
Ich bin gerade durch das Phoronix-Review der neuen AMD-Grafikkarten gegangen und bin beeindruckt. Sie testen die Leistung für Spiele unter Linux, mit und ohne Proton und mit AMDGPU-PRO sowie dem freien Mesa 20.3. Das Ergebnis ist einfach stark. Sie kommen alles zusammengerechnet auf dieses Benchmarkergebnis:
Die Treiber sind sehr nah beieinander, die 6800 XT is nah bei der RTX 3080 und die 6800 stärker als die 2080 Super. Zum Vergleich, so sieht das in meinem Meta-Benchmark unter Windows aus:
Das passt! Wenn auch die 3070 wahrscheinlich unter Linux anders als unter Windows etwas stärker als die kleinere 6800 ist, wird der Unterschied nicht groß sein. AMD hat also nicht nur eine Grafikkarte herausgebracht, die ordentlich unter Linux läuft. Das hatten wir schon in vorherigen Releases. Sondern AMD hat eine Karte herausgebracht, die es mit Nvidas neuen Top-Karten aufnehmen kann und deren relative Leistung unter Linux mit dem freien Treiber erhalten bleibt. Es gibt also endlich eine High-End-Grafikkarte für Linux mit einem freien Treiber.
Was großartig ist, auch wenn man selbst nicht unbedingt so viel für eine Grafikkarte ausgeben will. Die günstigeren Varianten werden ja ähnlich gut – relativ gesehen – unter Linux laufen.
Deadlight - hübsch vielleicht, doch gut?
Wednesday, 18. November 2020
Wenn ich Deadlight richtig einordne ist es eines dieser Indie-Spiele, für die Microsoft damals Publisher gespielt hat. So kamen die Spiele erst zur Xbox, dann auf den PC, die Entwickler wurden mehr als damals üblich unterstützt. Das war schon 2012, wobei 2016 ein Director's Cut herauskam.
Gute Sache, aber leider kein gutes Spiel. Deadlight versucht absolut mehr zu sein als ein einfacher 2D-Platformer. Da ist zum einen die Grafik, mit ihren ewigen Schatten und weitläufigen Hintergründen. Und zum anderen die Story, bei der die Zombieapokalypse mit soviel Emotionen wie möglich gefüllt wird. Die Hauptfigur Wayne hat seine Familie verloren, seine Reisegruppe wird angegriffen, die Überlebenden zu retten ist die Aufgabe. Dabei trifft er andere Überlebende, wovon einige ohne anfangs ersichtlichen Grund ihn angreifen. Soweit geht das alles in Ordnung.
Aber der Spielinhalt selbst stimmt nicht. Die Steuerung ist zu schwerfällig und fehleranfällig. Immer wieder bleibt Wayne hängen, springt nicht so schnell oder so weit wie ich will. Erklimmt er eine Kante, lässt er sich oft genug an ihr direkt wieder fallen anstatt weiterzulaufen. Und obwohl als Plattformer ein Controller die bessere Eingabemethode wäre, gibt es auch Fernkampfwaffen (eine Pistole, dann eine Schleuder – keine Waffe mehr, aber zum Betätigen entfernter Schalter) die mit der Maus tausendmal besser zu zielen sind als mit dem rechten Stick. Teilweise könnte das an Proton liegen, doch ist laut ProtonDB die Unterstützung nahezu perfekt.
Und dazu kommt (was dann garantiert nichts mehr mit Proton zu tun hat), dass der Grafikstil es an vielen Stellen nicht gerade einfach macht, schnell zu erkennen was Hintergrund und was ein erreichbarer Vorsprung ist. Die Sprungpassagen und die Level mit ihren Fallen werden durch ihr sowieso etwas abstruses Leveldesign, dem Grafikstil und dann noch den Steuerproblemen zu einem frustrierenden Ratespiel, bei dem meist nur mit viel Neuladen ein Abschnitt bewältigt wird.
Als Auflockerung sind überall in der Spielwelt kleine Sammelobjekte verteilt, zum Beispiel die Personalausweise Verstorbener. Doch warum sollte man die sammeln? Das Spiel gibt keine Belohnung. Manchmal finden sich Tagebuchauszüge von Wayne selbst, was eher aus dem Spiel rausreißt als interessant ist, denn wie sollen die dort hingelangt sein? Auch sind die Texte in dem Tagebuch erkennbar verzweifelte Versuche, dem ganzen mehr Tiefe zu geben. Sie sind zu lang und vom Spielgeschehen losgelöst, sie bringen dem Spielinhalt nichts dazu.
Bei Deadlight stimmt der Fokus einfach nicht, es hat zu wenig Substanz. Ich hatte von anderen Bewertungen beeinflusst ein nettes kleines Spiel mit guter Atmosphäre erwartet, doch so würde ich Deadlight jetzt definitiv nicht einordnen. Sondern eher als belanglos, frustrierend und unspaßig.
Age of Empires 2: Definitive Edition unter Linux
Monday, 16. November 2020
Die erneuerte Variante des Klassikers Age of Empires 2 läuft mit Proton unter Linux. Sogar der Multiplayer geht, aber nicht ohne weitere Anpassungen. Die Goldwertung auf ProtonDB ist da etwas irreführend.
Ich benutzte Proton-5.9-GE-8-ST, die modifizierte GloriousEggroll-Version. Damit lief das Offline-Match gegen die KI, ohne dass mir Probleme auffielen. Aber beim Multiplayer gab es immer fast sofort einen Disconnect.
Die Lösung fand ich auf Github:
rm ~/.steam/steam/steamapps/compatdata/813780/pfx/drive_c/windows/system32/ucrtbase.dll cd ~/.steam/steam/steamapps/compatdata/813780/pfx/drive_c/windows/system32/ wget "https://aka.ms/vs/16/release/vc_redist.x64.exe" cabextract vc_redist.x64.exe cabextract a10
Nach dem Ersetzen der Dateien lief dann auch der Multiplayer, bei mir bis jetzt stabil.
Update: Ich habe jetzt eine Weile nicht mehr gespielt, aber der Fix sollte weiterhin funktionieren. So landete erst vor drei Wochen – im Februar 2023 – in der ProtonDB ein Eintrag, laut dem Age of Empires mit einem cabextract-Workaround für den Multiplayer repariert werden konnte. Interessant ist die Abweichung, anstatt die vc_redist.x64.exe herunterzuladen kann sie dem Eintrag zufolge auch aus dem Hauptverzeichnis des Spiels kopiert werden. Und es funktionierte mit einer neueren Protonversion, Spieler sind also nicht auf die 5.9-GE8 beschränkt, sondern sollten einfach die aktuelle versuchen.
Multiplayer meinte bei mir übrigens nicht ranked, sondern ich spielte normale Multiplayermatche mit einem Kumpel. Bei der Microsoft-Accounteinbindung gab es auch Probleme, sodass ich manche dieser kleinen Aufgaben nicht erledigen konnte, die Avatar-Icons freigeschaltet hätten. Ich weiß nicht, ob diese Probleme Ranked-Spiele blockiert hätte – Erfahrungen dazu bitte gerne als Kommentar teilen.
Dafür hatte ich einen sehr positiven Eindruck von der Performance des Spiels. Auch gegen mehrere Gegner und mit vielen Einheiten auf der Karte sah ich keine FPS-Einbrüche. Gut, würde man bei einem so alten Spiel außerhalb extrem vollen Karten vielleicht auch nicht erwarten, aber Proton kann ja manchmal doch Performance kosten. Stabil war das Spiel auch, die Abstürze die ich sah hingen völlig an einer defekten Grafikkarte. Sobald die ausgewechselt war lief das Spiel bei mir komplett stabil.
7GUIs in Flutter (2/7)
Friday, 13. November 2020
Die sieben Aufgaben von 7GUIs sollen typische Problemstellungen bei der Anwendungsentwicklung widerspiegeln. Dann können die gebauten Lösungen dafür benutzt werden, verschiedene Programmiersprachen und Toolkits miteinander zu vergleichen.
Ich dachte, das ist eine gute Gelegenheit hier nochmal Flutter zu zeigen. Die Vorstellung letzten Monat zeigte relativ wenig von den damit baubaren Oberflächen.
Flutter ist ja deklarativ aufgebaut, das heißt die Oberfläche baut sich immer wieder neu und reagiert dann auf die neuen Variablenwerte. Man kann dafür StatefulWidgets
benutzen, die Variablen im Widget-Stateobjekt speichern und dann immer mit setState
anzeigen, dass die Oberfläche sich doch bitte neubauen soll. Ich werde stattdessen mit GetX der Oberfläche einen Controller zur Seite stellen, in dem die Variablen leben und der dafür sorgt, dass auch Widgets ohne reguläres Zustandsobjekt interaktiv sein können. Aber seht selbst:
1. Counter
Die erste Aufgabe ist ein Klickzähler. Doch einen Counter zu erstellen ist keine Herausforderung, das ist das Standardbeispiel auf der Flutterhomepage und bei Modulen wie GetX, die das Statemanagement übernehmen wollen. Entsprechend habe ich hier nur das GetX-Beispiel genommen und die Oberfläche angepasst.
So sieht es aus:
Die Oberfläche ist eine Row
, in der ein Textfeld und ein Button sind.
Das ist der Code:
import 'package:flutter/material.dart'; import 'package:get/get.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Counter', theme: ThemeData( visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(), ); } } class MyHomePage extends StatelessWidget { // Instantiate your class using Get.put() to make it available for all "child" routes there. final Controller c = Get.put(Controller()); @override Widget build(context) => Scaffold( appBar: AppBar(title: Text('Counter')), body: Center( child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ // Use Obx(()=> to update Text() whenever count is changed. Obx(() => Text("Clicks: ${c.count}")), RaisedButton(child: Text("Count"), onPressed: () => c.increment()), ], ))); } class Controller extends GetxController { var count = 0.obs; increment() => count++; }
Das Textfeld zeigt, da mit Obx
umschlossen, immer den aktuellen Wert der Zählvariable im Controller an. Ein Druck auf den Button erhöht diesen Wert.
Außer der Zählvariable im Controller und der Funktion increment()
hat die App keine weitere Funktionalität.
So funktioniert das dann in Bewegung:
Beachte auch, dass die Elemente wie die einer typischen Android-Anwendung aussehen. Das liegt schlicht daran, dass hier eine MaterialApp
gestartet wird.
2. Temperaturconverter
Die zweite Aufgabe ist eine Oberfläche zum Unwandeln von Celsius zu Fahrenheit und umgekehrt.
So sieht meine Lösung aus:
Das ist eine einzelne Row
, in der nacheinander je ein Texteingabefeld und ein Textanzeigefeld aufgereiht sind. Und ja, 0.0 bei beiden Feldern war nicht der richtige Defaultwert, wäre aber einfach änderbar.
Der Code:
import 'package:flutter/material.dart'; import 'package:get/get.dart'; // ... main() und MyApp sind weggekürzt class MyHomePage extends StatelessWidget { final Controller c = Get.put(Controller()); @override Widget build(context) => Scaffold( appBar: AppBar(title: Text('Temperature Converter')), body: Center( child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ SizedBox( width: 80, child: Obx(() => TextFormField( // to force Obx reload when value changes key: Key("C" + c.celsius.string), keyboardType: TextInputType.number, initialValue: c.celsius.value.toStringAsFixed(1), onChanged: (value) => c.fahrenheit.value = c.ctof(double.tryParse(value)), ))), Text("Celsius ="), SizedBox( width: 80, child: Obx(() => TextFormField( key: Key("F" + c.fahrenheit.string), keyboardType: TextInputType.number, initialValue: c.fahrenheit.value.toStringAsFixed(1), onChanged: (value) => c.celsius.value = c.ftoc(double.tryParse(value)), ))), Text("Fahrenheit"), ], ), )); } class Controller extends GetxController { var celsius = 0.0.obs; var fahrenheit = 0.0.obs; double ctof(double c) { return c * (9 / 5) + 32; } double ftoc(double f) { return (f - 32) * (5 / 9); } }
Hier passiert jetzt schon ein bisschen mehr. Der GetX-Controller hat zwei Variablen, celsius
und fahrenheit
, und zwei Funktionen zum Umwandeln der Werte. In der UI wird immer, wenn im Texteingabefeld etwas eingeben wird, mittels onChanged
im Controller der Wert des anderen Texteingabefeld geändert. Weil die Eingabefelder wieder mit Obx
umschlossen sind baut dann Flutter direkt die Oberfläche neu, mit dem neuen Celsius/Fahrenheit-Wert als initialValue
der Eingabefelder. Einen key
zu setzen hilft flutter bzw Obx dabei, zu erkennen wann die Oberfläche neu gebaut werden muss.
So sieht es in Bewegung aus:
Ich habe den Code auch auf Github hochgeladen, so kann jeder ihn direkt importieren und abändern. Es gibt noch fünf weitere Aufgaben, die immer komplizierter werden. Vielleicht mache eine Serie hierdraus, dann folgen sie später.
JJ Cale - The Woman That Got Away
Wednesday, 11. November 2020
Regions of Ruin – Toller Ansatz, aber es fehlt was
Monday, 9. November 2020
Regions of Ruin ist eine Genremischung in Pixelgrafik. Die Zutaten: Ein Sidescroller mit RPG- und Aufbauspielelementen. Man spielt einen Zwerg, der die aussterbende Zwergenrasse wiedervereinen will, dabei aber eine Monsterhorde voller Orks und Goblins besiegen muss.
Vom Lagerfeuer zum Dorf
Startpunkt ist die eigene Siedlung. Wo anfangs nur zwei Zwerge sich an einem Lagerfeuer wärmen entsteht nach und nach ein richtiges Dorf, wenn mit gesammelten Rohstoffen weitere Gebäude errichtet und ausgebaut werden. So kannst du zum Beispiel eine Taverne errichten. In der Taverne warten Söldner darauf, mit in die Gruppe genommen zu werden. Wird die Taverne vergrößert gibt es mehr Söldner zur Auswahl, dann kommt auch ein Brett für Kopfgeldquests hinzu. Die Rohstoffe wie Holz dafür werden nicht nur von dir selbst gesammelt, sondern auch von Arbeitern – meist befreiten Zwergen – die zu von Gegnern gesäuberten Gebieten geschickt werden können und dann von dort in einer festen Rate Rohstoffe einbringen. Solange der Vorrat reicht und das Lager noch nicht voll ist.
Diese Gebiete sind die auf der Karte verteilten und meist sehr kurzen 2D-Level. In ihnen gibt es NPCs – die oft Quests vergeben – Rohstoffe, Schätze und Monster. Um die Quests zu erfüllen muss man fast immer Monster töten, die auf dem oder einem anderen Level verteilt sind. Die werden schnell sehr stark, wohl dem Spieler der seine Söldnergruppe voll ausgebaut hat. Schätze meint Ausrüstungsgegenstände, wobei der Zwerg neben Rüstung, Waffe, Schild und Helm noch ein Amulett und einen Ring tragen kann. Wie beim Diablosystem haben manche dieser Items magische Eigenschaften und gibt es verschiedene farbig markierte Stärkeklassen, nach einer Weile müssen sie schon mindestens blau-magisch sein um eine Option sein zu können.
Hakelige Kämpfe
Die Kämpfe gegen die Monster und das Befreien der Level sind das Hauptelement des Spiels, und hier fängt es an problematisch zu werden. Anfangs alleine konnte man die Gegner sehr überlegt ausschalten, es gibt sogar eine Schleichfunktion, samt Verstecken hinter Fässern und Bonusschaden wenn Gegner überrascht werden. Treffer von Gegnern können Verletzungen verursachen, dann sinkt der Maximalgesundheitszustand, der Heiler in der Siedlung kann die Effekte entfernen. Angriffe können geblockt werden, es gibt Standard- und stärkere langsamere Angriffe, plus Ausdauerbalken, und mit bei Levelaufstiegen aktivierten Effekten kann der Spielercharakter angepasst werden.
Später jedoch ist man in der Gruppe unterwegs, und dann werden die Kämpfe sehr chaotisch. Gewinnen wird die stärkere Gruppe, aber welche das ist ist vorher kaum zu sehen. Die Level haben keine Gefährlichkeitsanzeige. So gibt es eine Hauptstory, in der Zwergenrunen aktiviert werden sollen, was dann jeweils ein entferntes Gebiet markiert. Das vierte davon war für mich und meine Gruppe viel zu stark – aber wie soll man erkennen, wann man für die Gegnergruppe bereit ist? Ich könnte jetzt nur Zeit investieren, mehr Level bewältigen, meinen Charakter und seine Gruppe aufleveln (auch mit einem Gebäude in der Siedlung, das Söldner viel stärker macht), und es dann periodisch wieder probieren. Aber das ist doch keine Lösung.
Abnutzungserscheinungen
Das wirkt auch deswegen so unattraktiv auf mich, weil sich jetzt langsam das Prinzip der 2D-Level abgenutzt hat. Wenn die Quests praktisch immer Töte ein paar Monster sind, wird das im Laufe der Zeit weniger interessant, selbst wenn das Spiel immer mal wieder neue Ideen aufbringt, wie (nicht toll umgesetzte) Festungen samt Monsterwellen, die man überleben soll. Gleichzeitig haben die regulären Gebiete das gleiche Problem: Die Monster sind teils zu stark, ohne dass das vorher ersichtlich ist. Sowieso, Grinden zu müssen ist kein gutes Spieldesign.
Die Bedienung ist dabei auch ein Problem. Ein Spiel wie Regions of Ruin spielt sich viel besser mit dem Controller. Das wird auch unterstützt, aber das Interface ist dafür nicht ausgelegt. Das Journal zum Beispiel lässt sich mit dem Controller gar nicht aufrufen, man muss die Maus in die Hand nehmen. Muss man auch, um im Söldnermenü den ausgewählten zu wechseln. Und sollte man, um auf der Karte Gebiete auszuwählen, da die Controllersteuerung dort nur manchmal und umständlich funktioniert. Problematisch wird auch das Schleichen: Jede der Schultertasten ist belegt, aber betätigt man aus Versehen die fürs Schleichen ist das ein Umschalter, der den Zwerg massiv verlangsamt – und damit im Gefechtschaos meist direkt tötet. Es ist offensichtlich, dass der Entwickler mit Maus und Tastatur spielt. Machen manche PC-Spieler auch bei solchen Spielen, macht den meisten aber keinen Spaß.
Stilfragen sind subjektiv, Bugs sind es nicht
Wenn die Grafik wenigstens konsistent hübsch wäre! Sie ist es stellenweise. Aber der Pixelmatsch bei den Charakteren und Gegenständen hat trotz der Anleihen mit der Eleganz der Spiele meiner Kindheit nichts zu tun. Immerhin wird die Freiheit einer solchen Engine genutzt, wenn jeder Ausrüstungsgegenstand am Spielercharakter zu erkennen ist (naja, soweit die Pixelfantasie trägt), die 2D-Gebiete voller Dinge und Hintergrundelemente vollstehen und in der Siedlung die Gebäude Schritt für Schritt wachsen. Aber trotzdem: Hübsch ist anders.
Voll zu den Problemen passt dann auch, wenn die Begleiter in den späteren vertikalen Leveln hängen bleiben. Nach unten fallen geht noch, aber dem Spieler springend folgen oder nach oben klettern - keine Chance. Oder wenn sie bei den Belagerungen an den Rand der Karte rennen und sich dort ohne Gegenwehr angreifen lassen. Da setzt die KI einfach komplett aus.
Fazit: Ein gutes Konzept macht noch kein gutes Spiel
Regions of Ruin hat mit seiner Mischung ein interessantes Konzept. Es fasziniert gerade zu Beginn, wenn all die Spielelemente anfangen zusammenzugreifen: Der Siedlungsaufbau, die hektischen Kämpfe, das Aufleveln des Charakters, dann die vielen Quests die Welt füllen und durch NPCs und gesammelte Buchseiten langsam die Hintergrundgeschichte zusammenkommt. Doch schnell verpufft dieser Effekt, wenn die kurzen 2D-Level zu wenig überraschen, die Kämpfe sich als repetitiv und zu chaotisch erweisen und der stark wachsende Schwierigkeitsgrad Grinden erfordert. Dazu die Schwächen bei Bedienung, Grafik und Begleiter-KI. Das erinnert an Pixel Piracy, das ähnlich faszinierend und dann unfertig war, wobei das mit etwas mehr Spielfortschritt auch noch unweigerlich kaputt ging. So kaputt ist Regions of Ruin nun nicht, doch auch es entpuppt sich nach wenigen Stunden leider als unfertiges Konzeptspiel.
Nette kurze Ablenkung: Guacamelee! Super Turbo Championship Edition
Friday, 6. November 2020
Während zum Día de Muertos im Wohnzimmer die Kerzen auf dem Ahnenaltar brannten spielte ich passenderweise Guacamelee!. Das Metroidvania bedient sich frei aus dem mexikanischen Totenkult und anderen Kulturelementen wie den Luchadores, Wrestlern, die in alten mexikanischen Fernsehserien als Superhelden gehandelt wurden.
Als solcher hinabgestürzt ins Totenreich muss der Spieler in einer etwa siebenstündigen Geschichte die Welt retten. Denn ein Oberskelett will mit einem Ritual die Welt der Toten und der Lebenden verschmelzen und dann beide beherrschen. Das klingt Ernst, ist es aber keinen Moment, denn die Geschichte und ihre Inszenierung ist komplett überdreht. Da spricht man mit einem Hahn der eventuell Satan ist, der Lehrmeister ist gleichzeitig eine Ziege, ein Zwischenboss ist eine zu einem Großskelett verschmolzene Mariachi-Band – und das alles untermalt durch den eigentümlichen, leicht abstrakten Grafikstil.
Jetzt habe ich dieses Jahr bereits Hollow Knight durchgespielt, was meinen Blick auf Guacamelee stark beeinflusste. Zum einen sind die beiden Spiele nunmal im gleichen Genre und ähneln sich entsprechend deutlich. Sprungpassagen, Kämpfe, Bosskämpfe, Sammelquests – es ist die gleiche Art Spielinhalt. Sie haben sogar viele der gleichen Upgrades, wie eine Flugfunktion.
Andererseits sind die Spiele auch sehr anders. Hollow Knight ist hübscher, viel länger, baut mit seiner Dark-Souls-artigen Unbestimmtheit eine melancholischere und deutlich dichtere Atmosphäre auf. Länger zu sein ist wirklich schon beim Spielen ein großer Unterschied, denn man merkt es stark an der Kürze von einzelnen Abschnitten Guacamelees. So fordert das Spiel auch weniger Durchhaltevermögen, wenn Kämpfe und Sprungpassagen zwar durchaus mal schwierig werden, aber nie so lange schwierig und ohne Zwischenspeicherpunkte aufgebaut sind, dass sie die Konzentrationsspanne überschreiten. Insgesamt war Hollow Knight sehr viel schwieriger, was mir nach der Umgewöhnung auf die leicht andere Tastenbelegung Guacamelee! abgesehen von einzelnen kniffligen Passagen recht einfach machte. Das gilt auch und gerade bei den Kämpfen, bei denen Guacamelee viel fehlertoleranter ist.
Anders betrachtet ist das auch ein Vorteil. Guacamelee frustriert viel seltener, eben weil die schwierigen Passagen und Kämpfe kürzer sind. Das passt gut zu der kurzweiligen Geschichte mit all den offenen und verstecken Witzen. Wo sonst ist es ein passendes Upgrade, sich in ein Huhn zu verwandeln! Und dann taucht Manny Calaveras Totenschädel auf einem Plakat auf. Wenn dann noch Zutaten für Enchiladas gesammelt werden sollen und generell jede Ecke (ein absurdes) Mexiko atmet, dann hat das was. So wie auch die mit gesammelten Münzen kaufbaren Kostüme nett sind, die Auswirkungen aufs Spiel haben. Der Satansanzug zum Beispiel verringert die maximale Lebensenergie, füllt sie dafür aber schon bei gelandeten eigenen Nahkampfangriffen wieder auf und nicht erst, wenn ein Gegner besiegt wurde.
Guacamelee! ist schon nett. Aber es nur ein Häppchen, eine nette kleine Abwechslung, die als solche nicht die Klasse anderer Metroidvanias wie Hollow Knight erreicht. Immerhin ist es ebenfalls kein teures Spiel und seinen Preis schon wert, besonders wenn es reduziert ist.
Object-Oriented Programming is Bad
Monday, 2. November 2020
Ich mochte diesen Vortrag. Man muss den Anfang mit seinem "das wichtigste Video ever" ignorieren, aber danach hat es ein paar gute Punkte (die sich nicht alle im zugehörigen Artikel finden). Es wird ziemlich überzeugend erklärt, was die Schwachstellen der objektorierentierten Programmierung sind: Dass dessen Ideal nicht erreicht werden kann und gar kein Ideal ist, warum Objektarchitekturen gerne ausufern und wieso man oft an Fragen gelangt, die nicht entscheidbar sind. Es sind zudem ein paar gute praktische Tricks drin die man in seinen Code einbauen kann, auch wenn die meisten nicht gerade neu sind. Aber sie sind gut gebündelt und im Kontext des Hauptarguments angemessen, lange Funktionen zu rechtfertigen und gekapselte lokale Funktionen ins Spiel zu bringen ist erfrischend.
Vor allem wird betont, dass Abstraktion nicht etwas positives ist, sondern automatisch Komplexität bedeutet, was sehr schlecht ist. Und das ist mindestens so richtig und wichtig wie es oft ignoriert wird. Es kollidiert mit vielem was gelehrt wird, aber ganz besonders mit den Paradigmen aus der Enterprise-Ecke. Man kann diesen Ansatz auch mit vielen gegenteiligen Entwicklungen im Javascriptuniversum vergleichen und wird ihn bestätigt finden.
Allerdings, was es für mich verdächtig komfortabel macht: Ich sehe einige Überschneidungen mit meiner angestrebten Programmarchitektur, die ich vor ein paar Jahren hier im Blog beschrieben habe und die sich seitdem nicht groß geändert hat. Wobei ich meinen Code wesentlich objektorientierter halte als es Brian Will (von dem der Vortrag stammt) wahrscheinlich gut fände. Doch wo der Code letztens Endes lebt ist in der Argumentation nicht so wichtig, solange er nicht unnötig abstrakt und über das Programm verteilt ist, und das war so ziemlich mein Hauptziel. Dieser Aspekt und die funktionalen Anleihen, die ich mittlerweile gerne einbaue, passen gut zu den Ideen des Videos.
Legend of Grimrock, altmodischer Crawler in neu
Monday, 26. October 2020
2012 einen Dungen-Crawler zu veröffentlichen, der abgesehen von der Grafik nur eine vorsichtig modernisierte Variante von Klassikern des Genres wie Eye of the Beholder ist, war ein gewagtes Experiment. Es scheint gelungen zu sein: Das Spiel bekam einen Nachfolger und Legend of Grimrock selbst erntete positive Reviews.
Ich tat mir mit dem Spiel anfangs allerdings ziemlich schwer. Ein Lands of Lore war das einzige Rollenspiel dieser Machart, das ich je gespielt hatte. Und es war keine zu positive Erinnerung, denn ich fand es zwar faszinierend, aber spätestens im Dschungelabschnitt wusste ich nie was ich machen sollte, verlief mich und starb. Ähnlich war mein erster Versuch mit Legends of Grimrock, als ich nach einer kurzen Weile schlicht an den Monstern scheiterte.
Jetzt motivierte mich aber der Blog The CRPG Addict mit seinen vielen Berichten über alte Rollenspiele, Grimrock nochmal eine Chance zu geben. Mittlerweile hatte ich auch verstanden, dass in diesem Genre Kämpfe aktiver geführt werden müssen als es erst scheint: Indem man immer um die Gegner herummanövriert kann man den meisten Schaden vermeiden. So sind die Gegner dann nicht mehr unbezwingbar, die anderen Mechaniken des Spiels werden erlernbar.
In Grimrock spielt man eine Gruppe von Abenteurern, die in einen Dungeon geworfen werden. Ihre einzige Hoffnung ist, ihn zu durchwandern und einen Ausgang zu finden beziehungsweise sein Mysterium zu lösen. Dabei ist der Dungeon unterteilt in mehrere Ebenen, immer musst du die Treppe in die nächstuntere finden.
Das beginnt simpel, mit wenigen langsamen Gegnern. Doch es zieht stetig an: Teleporter und Fallen kommen hinzu, mehr und stärkere Gegnertypen gilt es zu besiegen, mit immer besserer Ausrüstung und versteckten Schätzen (oft verborgen durch schwer zu sehende Schalter an den Wänden). Du musst Nahrung sammeln und die Helden versorgen, aber das Inventar ist nicht unbegrenzt groß und das Gewicht wird auch zum Problem. Solche Survivalelemente sind passend fürs Genre, aber erst in den letzten Jahren wiederentdeckt worden. Dazu kommen schließlich kompliziert werdende Rätsel.
Rätsel in Spielen sind immer ein zweischneidiges Schwert, so auch hier. Zu simpel, und man hätte sie sich auch sparen können. Zu schwer, und sie bremsen den Spieler komplett aus und zerstören jeglichen Spielspaß. Grimrock schafft es meistens, die richtige Balance zu finden. Doch einzelne Rätsel sind kaum zu lösen. So gibt es einen zwei Quadrate breiten Gang, den man in der richtigen Abfolge links/rechts entlanglaufen muss, sonst teleportiert er die Gruppe an den Anfang. Die richtige Schriftrolle mit einer Erklärung ist zwar vorher zu finden, aber kein Hinweis, dass genau an dieser Stelle die beschriebene Kombination anzuwenden ist. Ich war blockiert. Und wurde danach direkt nochmal blockiert: Denn auf dieser Ebene geht es erst weiter, wenn aus drei verteilten in die Wand gehauenen Altaren Gegenstände entnommen werden. Den zweiten davon brauchte ich aber nicht, weil mein Magier nur Feuermagie konnte, die Rolle aber einen Luftmagiespruch anbot. Also legte ich ihn zurück - und war wieder blockiert, bis ich schließlich die Lösung suchen musste. Altmodisch oder nicht, das ist einfach schlechtes Spieledesign.
Andererseits ist das Spiel trotz seiner betonten Altbackenheit ansonsten fair. Rätsel haben normalerweise erkennbare Lösungen, hat man das Kampfsystem einmal verstanden sind die Monster fordernd, aber besiegbar. Und der frustrierende Moment war nach dieser Situation: Während ich nach einem Levelaufstieg über die Skillpunktverteilung haderte, nachdem ich gerade erst die Ausrüstung neu verteilt hatte und dabei nicht wusste, ob der bei einem Treffer mehr Schaden verursachende Ogerhammer trotz der schlechten Genauigkeit oder die schwächere Streitkeule ohne Genauigkeitsmalus die bessere Wahl war, merkte ich: Hey, das ist ja wirklich wie in einem regulären Rollenspiel. Und hey, das macht sogar Spaß!
Legend of Grimrock hat eben hinter all dem Retroexperiment auch die Tugenden eines guten Computerrollenspiels. Die Gruppe des Spielers wird immer stärker, bekommt tolle neue Ausrüstung, die stärker ist und auch besser aussieht. Vor allem der Effekt der Skillpunkte ist wie so oft toll, wenn dann durch neue automatisch ausgelöste Attacken die Gegner viel schneller besiegt werden. Das motiviert. Die Hintergrundgeschichte wird sehr dezent erzählt, mit Notizzetteln und Traumsequenzen, aber sie ist da und hilft ebenfalls. Die vielen versteckten Schätze zu entdecken macht Spaß, wobei das fortwährende Absuchen der Wände nach versteckten Schaltern irgendwann auch nervig wird. Vor allem aber wird der Spieler selbst besser: Wenn mit etwas Erfahrung das Leveldesign lesbar wird, zum Beispiel wenn der Aufbau der Monsterköpfe an den Wänden vor einer Bodendruckplatte klar macht, dass man dort jetzt besser nicht drauftritt, sondern erstmal einen Stein hinlegt und ausweicht.
Insgesamt empfand ich Legend of Grimrock zwar durchaus auch als etwas mühselig, an einigen Stellen arg sperrig und verbesserbar. Aber es überwog, dass ich es interessant fand und es mich bis zum Spielende fesseln konnte. Wenn auch erst im zweiten Anlauf.
Katzenfutterautomat Honeyguaridan
Friday, 23. October 2020
Man beachte das Update unten
Der Honeyguaridan A36 ist eine Fütterungsmaschine für Katzen und Hunde. Programmierbar mit den Buttons bei dem Display oben lässt sich einstellen, wann wieviel Trockenfutter aus der Maschine kommen soll. Es gibt dafür sechs Slots, also könnten die Tiere sechsmal am Tag essen. Wieviel rauskommt wird in Portionen eingestellt, jede entspricht ungefähr 10 Gramm. 5 Gramm wäre eine bessere Portionsgröße (unsere bekamen zuvor abends 15g), aber 10g ist noch okay. Vor dem Füttern wird eine Audioaufzeichnung abgespielt – das halte ich für wichtig, so bekommen die Katzen ein klares Signal und müssen nicht rätseln, wann die Maschine anspringt. Das macht das Ganze aber auch nochmal lauter als der Mechanismus sowieso ist.
Mir war es wichtig, dass die Katzen nicht ans Essen kommen, dass die Futtermenge immer gleich ist und dass die Maschine kein Wlan hat, also auch keine App braucht. Außerdem kommt sie ans Stromnetz, so werden keine Batterien verschwendet, wobei man die optional nutzen kann. Das alles deckt der A36 ab.
Zusatz von wegen Stabilität: Natürlich habe die Katzen die erste und zweite Nacht über die Automaten gründlich bearbeitet. In dieser Phase hatte ich schwere Kochbücher draufgelegt, damit die Tiere den Automaten nicht umwerfen können. Die Bücher sind jetzt wieder weg, mittlerweile haben beide Katzen aufgegeben, die Automaten haben kein Futter preisgegeben.
Die Bedienung ist nicht super intuitiv, zum Beispiel war nicht klar dass der Deckel auf sein muss damit die Buttons funktionieren (es fehlte der erklärende Aufkleber). Aber mithilfe dieses Erklärvideos klappte es dann:
Wobei laut den Fotos auf Amazon eine neue Version mit anderen Buttons verkauft wird. Meine Automaten sind gebraucht gekauft und daher die ältere im Video gezeigte Variante.
Ich hatte nicht gesehen, dass sie sogar einen Splitter verkaufen um mit einem Automat zwei Tiere zu versorgen. Allerdings war in meinem Test ohne Splitter bei einer alternativen zweigeteilten Schale die Futterausgabe links und rechts nicht gleichmäßig, ich bezweifle daher, dass der Splitter fair funktionieren kann. Selbst wenn bräuchte ich mich nicht zu ärgern, denn zum Glück war der gekaufte zweite Automat kaum teurer als der Splitter.
Diese Automaten sind praktisch. Es geht weniger darum, dass sie Arbeit sparen – das Nassfutter müssen wir ja sowieso noch selbst geben. Aber da jetzt der Automat den Morgen und den späten Abend abdeckt, haben die Katzen keinen Grund mehr uns morgens zu wecken bzw abends zu nerven. Bewahrt vor allem das Wochenende, an dem sie sonst wie werktags frühmorgens von uns Futter erwarten würden. Ich hatte vor dem Kauf lange gezögert, zu lange denke ich jetzt.
Update 07.04.2022: Mittlerweile sind die Automaten hier eine Weile in Benutzung und haben ihre Schwächen preisgegeben.
Die umständliche Bedienung wird zum Problem, wenn man zwei Automaten hat. Deren Uhr zu synchronisieren ist schwierig, man muss zu zweit sein und sich genau abstimmen. Das sollte nicht nötig sein.
Einer der beiden Automaten blockiert mittlerweile öfter. Es ist nicht klar warum, in beiden ist das gleiche Futter drin, die Größe der Brocken kann nicht das Problem sein. Der Automat verteilt dann einfach kein Essen, was die eine Katze irritiert zurücklässt. Es leuchtet dann das rote HG in der Mitte. Schläge helfen manchmal, aber nicht zuverlässig.
Der andere dagegen gibt manchmal ohne Anlass Essen aus – ohne Witz, sogar genau beim Schreiben dieses Updates passierte das wieder. Das ist selten genug, aber diese Unregelmäßigkeit ist vll auch ein Grund dafür, warum meine Katze lange Zeit vor der Fütterung vor dem Automaten auf den großen Moment wartet.
Eine meiner Katzen hat einen Weg gefunden, mit der Pfote in die Öffnung zu gehen und dem Automaten Futter zu entlocken. Das klappt nicht immer, sonst würde sie den Tag über nichts anderes machen, aber regelmäßig. Eine andere Methode ist das Umschmeißen des Automats, das dann irgendwann doch wieder begonnen hat. Jetzt liegen dementsprechend wieder Gewichte auf dem Gerät, was aber kein Allheimittel ist, denn auch die können von den Tieren heruntergeworfen werden, der Automat folgt dann. Es fehlt ein Ankermechanismus oder zumindest ein paar Kilo Gewicht.
Das ganze wird noch eine Weile beobachtet werden, vielleicht versuche ich den blockierenden mal auseinanderzunehmen und zu reparieren. Aber ansonsten werde ich einen anderen Futterautomaten probieren müssen. Den Honeyguaridan A36 kann ich daher inzwischen nicht mehr empfehlen.
Flutter: Ein tolles Framework für mobile Apps
Monday, 19. October 2020
Flutter ist ein Framework zur Anwendungsentwicklung. Fokus sind Android und iOS-Apps, aber man kann stattdessen auch für das Web oder mittlerweile sogar den Desktop entwickeln. Es ist ein Google-Projekt, was aber nicht wirklich ein Nachteil ist wenn man mit Android sowieso für eine Google-Plattform entwickelt.
Das Framework ist schon wegen der Konzepte eine ziemliche Umstellung zur gewöhnlichen Android-Entwicklung, zusätzlich schreibt man die Anwendung dann auch noch in einer neuen Sprache, Dart. Doch Dart entpuppt sich als großer Vorteil, dazu unten mehr.
Flutter-Code und Konzepte
Flutter mag insgesamt eine große Umstellung sein, doch der Einstieg ist super simpel. Alles ist ein Widget, jede Oberfläche ist ein Widgetbaum, im Baum kombiniert man die Widgets frei. Zum Beispiel ist das hier das mitgelieferte Hello-World-Beispiel:
import 'package:flutter/widgets.dart'; void main() => runApp( const Center( child: Text('Hello, world!', key: Key('title'), textDirection: TextDirection.ltr ) ) );
So sieht das aus:
Auch als Anfänger ist das problemlos lesbar, die Elemente machen genau genau was man erwartet. main()
ist der Startpunkt der Anwendung, runApp
startet die App, das Center-Widget
zentriert, Text
zeigt Text an. Würde man den Code in eine Material-App packen, würde die App auch direkt aussehen wie eine typische Android-Anwendung.
Aber es ist purer Code, während man bei der Entwicklung mit Android-Studio Oberflächen mit XML-Layouts zusammenbauen konnte. Die Trennung nicht zu haben birgt die Gefahr, UI-Code und Programmlogik zu verschmelzen – was ja aber auch bei regulären Androidanwendungen nur zu schnell passiert. Flutter, und mehr noch die Community drumrum, kompensiert diesen Nachteil über mit einem exzessiven Fokus auf Zustandsmanagement.
Zustandmanagement
Zustandsmanagement ist die Verwaltung des States, das sind einfach die lokalen und globalen Variablen, die Einfluss auf die Oberfläche haben. Flutter versucht generell eine deklarative UI-Entwicklung vorzugeben: Die UI wird immer wieder neu gezeichnet, und wenn der Zustand sich verändert hat sieht die Oberfläche entsprechend anders aus.
Zuerst aber gibt es StatelessWidgets, die keinen eigenen Zustand haben. Holen sie sich die Variablen nicht von außen sehen sie immer gleich aus.
class GreenFrog extends StatelessWidget { const GreenFrog({ Key key }) : super(key: key); @override Widget build(BuildContext context) { return Container(color: const Color(0xFF2DBD3A)); } }
Das hier wäre einfach ein grünes Fenster.
Dann gibt es StatefulWidgets, die ihre eigenen änderbaren Variablen haben können.
class YellowBird extends StatefulWidget { const YellowBird({ Key key }) : super(key: key); @override _YellowBirdState createState() => _YellowBirdState(); } class _YellowBirdState extends State<YellowBird> { @override Widget build(BuildContext context) { return Container(color: const Color(0xFFFFE306)); } }
Das zeigt jetzt auch nur einen farbigen Bereich an. Aber der State-Klasse könnte man jetzt Variablen hinzufügen. Die Doku zeigt dieses Beispiel:
class Bird extends StatefulWidget { const Bird({ Key key, this.color = const Color(0xFFFFE306), this.child, }) : super(key: key); final Color color; final Widget child; _BirdState createState() => _BirdState(); } class _BirdState extends State<Bird> { double _size = 1.0; void grow() { setState(() { _size += 0.1; }); } @override Widget build(BuildContext context) { return Container( color: widget.color, transform: Matrix4.diagonal3Values(_size, _size, 1.0), child: widget.child, ); } }
Das ist also ein Widget mit einer festen Größe, das eine Funktion mitbringt um zu wachsen. setState(() { });
muss immer dann aufgerufen werden, wenn die Oberfläche die Zustandsvariablen neu evaluieren soll.
GetX
Es gibt Unmengen Lösungsansätze für die Zustandsverwaltung. Zum einen weil sie wichtig ist, denn mit ihr steuert man ja direkt das Verhalten der App. Aber auch, weil da die Einflüsse der Entwicklergruppen kollidieren: Da kommt das übliche mentale Chaos aus dem Javascript-Land angeschwemmt, vermischt mit Enterprise-Architekturen – passt ja zu einem Google-Framework. GetX ist anders. Es ist ein pragmatisches Werkzeugset, das nur unter anderem Statemanagment löst. Mit dem Flutter-Modul kann man jeder Oberfläche einen Controller zur Seite stellen, und mit dem Obx
-Widget die Oberfläche immer neu zeichnen lassen wenn sich eine der Variablen ändert. Im Flutter-Kontext ist das Magie, das Resultat ist einfach sauberer Code. So ist das Counter-Beispiel aus der Readme elegant und verständlich:
Du hast einen Controller, in der die Variable und eine optionale Manipulierfunktion definiert wird:
class Controller extends GetxController{ var count = 0.obs; increment() => count++; }
Und dazu das Widget, bei dem ein Druck auf den Button die Variable erhöht, wodurch direkt die Anzeige aktualisiert wird:
class Home extends StatelessWidget { // Instantiate your class using Get.put() to make it available for all "child" routes there. final Controller c = Get.put(Controller()); @override Widget build(context) => Scaffold( // Use Obx(()=> to update Text() whenever count is changed. appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))), // Replace the 8 lines Navigator.push by a simple Get.to(). You don't need context body: Center(child: RaisedButton( child: Text("Go to Other"), onPressed: () => Get.to(Other()))), floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment)); } class Other extends StatelessWidget { // You can ask Get to find a Controller that is being used by another page and redirect you to it. final Controller c = Get.find(); @override Widget build(context){ // Access the updated count variable return Scaffold(body: Center(child: Text("${c.count}"))); } }
Das Beispiel zeigt noch zusätzlich die Navigationslösung, die man nicht nutzen muss. Trotzdem: Klarer geht es nicht.
GetX hat Nachteile. So sind die Details nicht ganz so simpel wie es zuerst scheint - wie zum Beispiel umsetzen, dass auf eine Objektvariable eines Listenelements reagiert wird, wenn doch List.obs
nur auf das Vergrößern und Verkleinern der Liste reagiert? Vor allem aber ist es ein junges Projekt, bei dem noch zu viele Commits hereinkommen, von zu wenigen Entwicklern, das Ding ist noch im Flux. Trotzdem ist es ein supermächtiges Werkzeug und erleichtert die Arbeit mit Flutter deutlich.
Paketverwaltung
Richtig nett an Flutter ist auch das darum aufgebaute Entwickleruniversum. Man findet Erklärvideos und -artikel. Am wichtigsten aber sind die Pakete. Auf https://pub.dev/ gibt es eine Suche, Module (für Flutter wie Dart) werden dort sauber vorgestellt, gewonnene Likes dienen als Orientierungshilfe. Bräuchte meine App beispielsweise eine animierbare Navigationszeile unten, ich könnte sie mit convex_bottom_bar direkt von dort beziehen. Eintragen in die pubspec.yaml, flutter pub get
ausführen, den Code einbauen, fertig. Eine solche Paketübersicht habe ich bei der direkten Androidentwicklung mit Java schmerzlich vermisst.
Dart
Dass Flutter direkt eine neue Programmiersprache erfordert ist erstmal total abschreckend. Doch Dart macht das ganz schnell wieder wett, denn die Sprache ist gelungen. Dart ist eine wilde Mischung, für mich fühlt es sich aber vor allem nach Ruby an. Was toll ist. Es fehlen allerdings die Blöcke. Dafür hat es async-Funktionen direkt mit dabei, samt await und Future. Wikipedia zählt Smalltalk und Erlang als weitere Einflüsse. Bestimmt ist da auch Python mit drin, Java und Javascript.
Dart hat mich bisher noch in keiner Situation enttäuscht. Die Sprache hat für mich als Ruby-Programmierer immer eine gute Lösung parat gehabt und mich seltenst negativ überrascht.
Die folgenden Beispiele kann man auch alle auf der Webseite ausprobieren.
Hallo-Welt:
main() { print("Hello, World!"); }
Zahlen:
int i = 1 + 2; print(i); // => 3
Strings:
final s = "abc" + "def"; print(s); // => abcdef
Es gibt Listen und Maps (Hashes):
var testliste = [1, 2, 3]; print(testliste[1]); // => 2 var testmap = {1: 'a', 2: 'b', 3: 'c'}; print(testmap[2]); // => b
Und map und fold:
var testliste = [1, 2, 3]; var result = testliste.map((element) => [element]); print(result); // => [[1], [2], [3]] var folded = result.fold(0, (prev, element) => prev + element.first); print(folded); // => 6
Dr ganze Bereich um async war ungewohnt und ist nicht einfach, aber das simple Beispiel ist klar:
Future<void> fetchUserOrder() { // Imagine that this function is fetching user info from another service or database. return Future.delayed(Duration(seconds: 2), () => print('Das hier folgt 2 Sekunden später')); } void main() { fetchUserOrder(); print('Das ist die erste Ausgabe.'); }
Und natürlich Objekte mit Funktionen:
class Sword { int damage = 5; use() => print("$this dealt $damage damage."); } main() { var sword = Sword(); sword.use(); // => Instance of 'Sword' dealt 5 damage. }
Dazu kommt noch viel mehr: Generic, Mixins, Named Parameters, Lambda hatte ich erwähnt, die Standardklassen haben hilfreiche Funktionen definiert, chaining mittels ..
– also das Aneinanderreihen von Funktionen an ein Objekt, obwohl die Funktionen eigentlich void
zurückgeben. Dart ist wirklich erstaunlich angenehm.
Und die Flutter-Programme, die mit Dart gebaut werden, laufen nicht etwa in einem Wrapper, sondern werden kompiliert und dann nativ auf dem Gerät ausgeführt. Dart kann auch nach Javascript transpilieren, was dann die Web-Unterstützung von Flutter erlaubt, aber das ist gefährlich – die so erstellten Webseiten sind bestimmt Javascript-Monster. Für einzelne Spezialfälle könnte aber auch das eine gute Lösung sein.
Fazit: Beachtenswert
Flutter verspricht, mobile Anwendungen viel schneller entwickelbar zu machen als wenn man sie mit den Bordmitteln schreibt. Wenn man das Framework bereits beherrscht mag das stimmen. Ist man neu, ist es mit der Fixierung auf indirekte Interaktivität nicht so einfach zu beherrschen und dieser Lernprozess dann auch nicht schnell. Aber: Dann ist es mächtig, und ich habe definitiv den Eindruck, dass man hiermit bessere Oberflächen und somit auch Apps bauen kann. Dass die dann auf beiden großen mobilen Betriebssystemen laufen können kommt noch dazu.
Dart ist dann die Krönung. Erwartet hatte ich ein alternatives Javascript – etwas, womit man arbeiten kann, aber woran man eher keine Freude hat. Stattdessen ist es nahe genug an Ruby und so ausgereift, dass ich fast frei meinen Programmcode schreiben kann. Sicher, mit der Zeit werden sich die Schwachstellen offenbaren, und eine so junge Sprache kann nicht an die Modulvielfalt von Ruby herankommen. Eine positive Überraschung war sie aber allemal.
Wenn Flutter die entsprechende Entwicklung stabilisiert (noch ist es alpha) könnte ich mir das glatt auch für Linuxanwendungen vorstellen. Die Entwicklung mit wxWidgets, GTK und Qt ist deutlich komplizierter. Derzeit greifen Entwickler dann stattdessen oft zu Electron und anderen Javascript-Desktopwrappern. Flutter mit Dart könnte die bessere Lösung sein. Ich bin gespannt, ob sich das bewahrheitet. Derzeit hat sich die Alpha an snap gekoppelt, was komplett inakzeptabel ist solange es die einzige Lösung bleibt. Aber Flutter-Appimages oder schlicht Binaries? Könnten eine tolle Sache sein.
Wer jetzt für Android entwickeln will, für den ist Flutter mit Dart auch jetzt schon einen Versuch wert.
Fallout New Vegas Jubiläumsartikel auf GamersGlobal
Sunday, 18. October 2020
Auf GamersGlobal haben wir eine kleine Aktion umgesetzt. Zum zehnjährigen Jubiläum des Spiels haben wir zeitgleich zwei Artikel veröffentlicht. Einmal von SupArai einen Fast-Erstkontakt mit dem RPG-Klassiker, bei dem es darum ging ob dieser Fallout-Teil ihn heute noch fesseln konnte. Und ich schrieb über die vier Storyerweiterungen, stellte sie vor und ordnete sie ein.
Ich hatte ja auch hier im Blog über die Erweiterungen geschrieben, der GG-Artikel ist aber eigenständig. Wer noch mehr über die Addons lesen will kann sich die Blogartikel zusätzlich anschauen.
Wir waren fertig!
Monday, 12. October 2020
Jono Bacons Bemerkungen zur Brainstorm-Abschaltung haben bei mir eine Erinnerung freigesetzt. Als Ubuntu bekannt wurde war ich ja bei ubuntuusers aktiv, trat als Supporter dem Team bei und verbrachte viel Zeit in dem Forum. Und wenn ich natürlich nicht für alle reden kann, war zumindest bei mir die Grundeinstellung bezüglich dieses Linuxsystems: Das wars jetzt.
Ob Gnome 2, das damalige KDE oder die unbekannteren Fenstermanager: Die Desktopmetapher war perfektioniert. Seit Windows 95 hatte sich da nichts mehr getan, Microsoft produzierte Schrott (Vista! Wobei, das war etwas später), warum sollte sich da jetzt noch etwas ändern? Der Linuxkern untendrunter funktionierte gut, soweit wir sehen konnten. Es ging nur noch darum, die richtigen Standardeinstellungen zu finden, Bugs zu beheben, Workarounds umzusetzen, Konfigurationssoftware zu bauen.
Es ist diese Einstellung, die erklärt, warum ein Ziel wie "Fixt Suspend/Hibernate auf allen Laptops" richtig erschien. Was sonst sollte man machen? Die Iconfarben ändern?
Natürlich ist genau das passiert.
Auf der einen Seite stimmte die Einstellung damals: Es hat sich nichts mehr getan. Selbst das so disruptive Gnome 3 ist letzten Endes, von der Oberfläche her, Windows 95 kaum überlegen. Die Änderungen sind im Zweifel Änderungen, nicht Verbesserungen, was die Oberfläche besser macht sind Funktionen wie virtuelle Desktops, die die Linuxwelt schon vorher hatten. Compiz kam und verschwand wieder, der 3D-Desktop war genau so unbrauchbar wie er zuerst schien. Wir benutzen immer noch Maus und Tastatur und Monitor, und gerade bei den Eingabegeräten oft genug exakt die gleichen wie damals.
Aber andererseits war die Erwartung, dass nun zielgerichtet das System perfektioniert wird, komplett falsch. Stattdessen wurde umgekrempelt was umzukrempeln ging, kämpften die verschiedenen Distributionen und ihre technischen Initiativen um die Vorherrschaft. Ja, im Großen geht es vorwärts, sehen unsere Oberflächen nun hübscher aus als damals und haben Animationen. In manchen Bereichen wie der Spieleunterstützung ist der Fortschritt so massiv wie hervorragend. Aber es ist oft keine direkte Vorwärtsbewegung, es ist ein wildes Umherzucken und man muss bei jeder Bewegung froh sein, wenn nicht gerade kaputt geht was vorher perfekt funktionierte. Pulseaudio ließ hunterttausendfach Computersoundsysteme verstummen. Die beiden bekannten Desktopumgebungen entschieden nahezu zeitgleich, von null anzufangen und ihre stabilen Versionen mit instabilen und dem üblichen Deskop fernen Experimenten zu ersetzen, woran das Gnome-Projekt beinahe zugrunde gegangen wäre als die Nutzer rebellierten. X sollte ersetzt werden durch Mir und Wayland, Mir starb, Wayland versucht sich immer noch als schlechteren X-Ersatz. Systemd nistete sich als seitdem immer weiter wachsendes Krebsgeschwür unter dem Deckmantel eines Initsystem in die Distributionen ein, machte Logs binär und Startprozesse so instabil wie unwartbar. Und das sind ja nur die großen Beispiele, die üblichen Verdächtigen.
Diese Sichtweise erklärt dann auch die Abwehrbewegungen. Wer wie ich seit über zehn Jahren den gleichen Nischen-Fenstermanager einsetzt (wenn auch mit einem schöneren Design) hat kein Interesse daran, wenn das Fundament wackelig wird. Und genau: Die Linuxwelt hat sich getrennt, mit Extremen wie Fedora auf der einen Seite – die Wayland einsetzten, obwohl das System längst noch nicht als X-Ersatz taugte, und SELinux anschalteten, obwohl dann in vielen Situationen der unbedarfte Nutzer komplett blockiert wurde – und Anti-Systemd-Distributionen wie Devuan oder auch void auf der anderen, und dazwischen Distributionen wie Ubuntu, die nach schmerzhaften Ausbruchversuchen wie Unity generell der Masse folgen.
Aber wenigstens ist der vorher braune Desktophintergrund jetzt pink.