COOKIES

This site may be using cookies to melk you with your own data. I, ben0bi, am not the owner of this web service and I also do not maintain their servers. But the EU and the owner of this service think, that the user (me) has the responsibility to inform the consumer (you), that this website uses cookies. Again: I, ben0bi, NEVER use cookies. I am not responsible for the setup of this web service. I just present some information here and do not intend to spy on you for whatever reason ever. But (also again), I do not host this website nor do I maintain any servers related to this website nor do I benefit from using the cookies maintained from this service. I hereby give the responsibility for using cookies on blogspot back to the owners of blogspot.

Mittwoch, 29. März 2023

GODOT 4: AnimatedSprite mit AnimationPlayer

 Dieses Tutorial gibt es in Video Form. Leider wurde der Sound nicht aufgenommen. Es ist also sehr ruhig. Ich habe dafür Text drauf geklatscht.

Im neuen AnimatedSprite von Godot 4 (stable) hat es keine Play-Checkbox mehr. Ich habe deshalb die Animationen mit einem AnimationPlayer-Node erstellt. Dieses Tutorial erklärt, wie das geht.

Hint: Der Loop-Button für den AnimationPlayer ist gleich unter dem Porträt.

https://www.youtube.com/watch?v=JuFzqgnIyvI


Viel Spass.

Donnerstag, 29. Dezember 2022

(GAME RELEASE) Pueblo

 


Ein kleiner Top-Down-Shooter für euch um unser Firmenkonto aufzubessern.

Veröffentlicht am 9.Dezember 2022

Entwickelt von Benedict Jäggi

https://store.steampowered.com/app/2219910/Pueblo/

Nur 2.50 CHF auf Steam.


Have Fun!

Samstag, 15. Oktober 2022

(GAME RELEASE) Exit Trip


Das ist mein erstes wirklich veröffentlichtes Game. Ein kleines Roguelike Game im Browser. 


Das Spiel ist schon seit 2019 online doch erst jetzt gibts den "Release" dazu.

Viel Spass!

Hier ist der Source Code, das Spiel wird direkt aus dem Github Account heraus geladen.

Dienstag, 17. Mai 2022

GODOT: HearthBar


Eine Lebensleiste aus Herzen ist in fast jedem Zelda vorhanden. Hier erkläre ich, wie ich das in Godot gelöst habe. Es hat pro Herz fünf Einstellungen: Empty, Quarter, Half, FourQuarter und Full. Im Bild siehst du Full, Half und Empties.

Erstmal solltest du das folgende Spritesheet mit all den Herzen herunterladen und in deinem Assets-Ordner einfügen. In Godot, klicke das Bild an und dann oben links neben "Szene" auf "Importieren". Deaktiviere das Filter-Flag und reimporte das Bild.

Dieses Tutorial ist eine "Erweiterung" von diesem Tutorial. (Auf Englisch)

Rechtsklick & Speichern bitte...

Nun brauchen wir ein Objekt, welches ein Herz enthält. Klicke auf "Szene"->"Neue Szene" und stelle ein "Sprite" als Root-Node ein.

Ich habe es "Sprite_Hearth" genannt.

Lade das heruntergeladene Spritesheet als Textur von Sprite_Hearth (Bild im Inspektor auf "Textur" ziehen)

Das Spritesheet hat 5 12x12Pixel Bilder.

Stelle im Inspektor unter "Region" die Werte "w" und "h" auf jeweils 12 und "Region enabled" auf true.

Speichere die Szene als "HearthContainer.tscn" ab.

In deiner Main-Szene:
(Du hast hoffentlich ein CanvasLayer für dein GUI)

Hänge eine Node ("Node") an dein CanvasLayer. Positioniere die Node oben links im Bildschirm.
Hänge daran ein Script mit folgendem Inhalt:

extends Node

# wir laden die vorher erstellte Szene in eine Variable
var hearthTile = preload("res://Assets/GUI/HearthPack/HeartContainer.tscn")

# deletes all child nodes from that node
func delete_all_child_nodes():
  for n in self.get_children():
    self.remove_child(n)
    n.queue_free()

# erstelle alle herzcontainer und fülle sie
func add_hearts(var player):
  # erstmal werden alle child-nodes gelöscht und danach neu aufgebaut
  delete_all_child_nodes()

  # damit herz nicht aus dem bildschirm herausragt gibt
  # es initial die position 6,6 statt 0,0
  var xpos=6
  var ypos=6
  # q braucht es für die y-linien
  var q=0
  # volle herzen sind multiplikanten von 4
  var healthdivided = int(player.health*0.25)

  # welchen status hat das letzte gefüllte herz?
  var healthmod= int(player.health) % 4

  for i in player.max_health*0.25:
    var hc= hearthTile.instance() # erstelle eine neue herzcontainer instanz
    self.add_child(hc) # hänge sie an die eigene node an.
    # setze die position
    hc.position.x=xpos
    hc.position.y = ypos
    # fülle die das aktuelle herz
    if i<healthdivided:
      # full hearts
      hc.region_rect.position.x=4*12.0 # letzte position
    elif i==healthdivided:
      hc.region_rect.position.x=healthmod*12 # mit mod herausgefundene position
    else:
      # empty hearts
      hc.region_rect.position.x=0

    q+=1
    xpos+=12 #setze die neue x position
    # reset a line down
    if q>=10:
      q=0
      xpos=6
      ypos+=12

Es geschieht folgendes: Jedes mal, wenn das Signal "on_stats_changed" mit dem Player als Parameter emittiert wird, werden erstmal alle Herzen in der Node (mit dem Skript) gelöscht. Danach wird auf einer Linie durchgegangen und maximal 10 (zehn) Herzen erstellt. Wenn es mehr als 10 Herzen hat, wird danach eine Linie runtergegangen.
Bei der Erstellung der Herzen wird geprüft, ob das Herz gefüllt oder leer ist. Wenn es das letzte Herz nach den Gefüllten ist, wird noch geprüft, welche Region des Spritesheets benutzt wird, ansonsten ist die Region xy = 0,0 für ein leeres Herz oder 4*12,0 für ein volles Herz.
Die Funktion add_hearts muss nun nur noch bei jeder Änderung des Health-Statuses aufgerufen werden. Das macht man am besten mit Signalen.

Dein Player Script sollte noch folgendes enthalten:
var health = 12 # 3*4 = 3 hearts
var max_health = 12 ...
signal on_stats_changed
Und in allen Funktionen des Players, welche die die health-Variable verändern am Ende noch:
emit_signal("on_stats_changed", self)

Mit self wird der Player an das Signal übergeben, so dass man die Health-Variable auslesen kann. Konnekte nun noch das Signal on_stats_changed im Inspektor mit dem Hearts-Script in der (unbenannten) Node und schreibe dazu diese eine Linie Code (der Funktionsname wird automatisch generiert):
# the player emits a signal when the stats change.
func _on_Player_player_stats_changed(var player):
  add_hearts(player)

Fertig.
Also, bei mir gehts... ;)

Samstag, 7. Mai 2022

GODOT: Konsole

In diesem Post wird in der Godot Engine eine Konsole erstellt. Sie wird automatisch an die Bildschirmgrösse angepasst.



1. Erstelle eine neue Szene "Console" in deinem Projekt mit einem CanvasLayer namens Console als Root Node. Stelle im Inspektor bei Node die Pause-Mode auf "process". Hänge daran ein "Panel"
    Das Panel sollte auf der Position 0,0 sein und ein bisschen vergrössert werden für die Übersicht.

2. Erstelle unter dem Panel folgende Dinge - du musst sie NICHT anordnen, da wir das im Code machen:     
    2.1. Ein Label "LabelConsole" mit dem Text "Console" Dieses ist der Titel des Fensters. Bitte ordne dieses Label in der linken oberen Ecke des Panels an. Nur dieses Label muss angeordnet werden.
    2.2 Einen Button "BtnExit" mit einem "X" als Text.
    2.3 Einen Button "BtnClear" mit "Clr" als Text.
    2.4 Ein TextEdit-Node namens "ConsoleText": Hier kommt der Text von der Konsole rein.
    2.5 Ein LineEdit-Node namens "EditText": Hier schreibt der User seinen Text rein.

3. Hänge an das Root-Node ("Console"-Panel) ein Skript namens "Console.gd" an. 

Hier ist der Script Code:
Erstmal ein paar Variablen und überall genutzte Komponenten:
Mit den Variablen kann man im Editor einstellen, wie gross das Fenster relativ zur Applikationsgrösse ist und ob es zentriert werden soll. Die Signale sind dazu da, um einige Sachen vom Game einzustellen, wenn die Konsole hervorgeholt oder versteckt wird, wie zum Beispiel den GameState auf Pause zu setzen.

# multipliers for the size.
# its x times screensize so the multipliers should
# be between 0.0 and 1.0
export var hSize_Multiplier: float = 1.0
export var vSize_Multiplier: float = 0.75
export var centerHorizontally: bool = false
export var centerVertically: bool = false

# components
onready var TxtEdit = get_node("EditText")
onready var ConsoleTxt = get_node("ConsoleText")
onready var BtnExit = get_node("BtnExit")
onready var BtnClear = get_node("BtnClear")

# signals
signal console_shown
signal console_hidden
signal console_command_given
Die Signale kann man nun im Editor mit dem Code verknüpfen. Das muss man ausserhalb der Konsolen-Szene machen, sonst ändert man nur den Code von der Konsole...

In der ready-Funktion wird die Konsole erstmal angeordnet durch die resize-Funktion:
func _ready():
  resize()

# set size of the console, align all UI elements
# and maybe center it.
func resize()->void:
  # clamp the multiplier
  if vSize_Multiplier<0: vsize_multiplier="0<br">   if vSize_Multiplier>1.0: vSize_Multiplier = 1.0
  if hSize_Multiplier<0: hsize_multiplier="0<br">   if hSize_Multiplier>1.0: hSize_Multiplier = 1.0

  # set resolutions
  # first get the window resolution and multiply it
  var screenRes := Vector2(ProjectSettings.get_setting("display/window/size/width"), ProjectSettings.get_setting("display/window/size/height"))
  var consoleRes := Vector2(screenRes.x * hSize_Multiplier, screenRes.y*vSize_Multiplier)
  print("Console Resolution: ",str(consoleRes.x),"/",str(consoleRes.y))
  # set resolution to the panel
  self.rect_size = Vector2(consoleRes.x, consoleRes.y)

  # set position of the panel
  if screenRes.x != consoleRes.x and centerHorizontally == true:
    var halfx=consoleRes.x*0.5
    self.rect_position.x = screenRes.x*0.5-halfx
  if screenRes.y != consoleRes.y and centerVertically == true:
    var halfy = consoleRes.y*0.5
    self.rect_position.y = screenRes.y*0.5-halfy

  # set resolution to the edit textbox
  TxtEdit.rect_size.x = consoleRes.x
  TxtEdit.rect_position.y = consoleRes.y- TxtEdit.rect_size.y
  # set position of the buttons.
  BtnExit.rect_size=Vector2(20.0,20.0) # there was an error aligning this, it growed
  BtnExit.rect_position = Vector2(consoleRes
.x-BtnExit.rect_size.x,0)
  BtnClear.rect_size.y = BtnExit.rect_size.y
  BtnClear.rect_position = Vector2(consoleRes.x-BtnExit.rect_size.x-BtnClear.rect_size.x,0)
  # set resolution of the console text box
  ConsoleTxt.rect_position = Vector2(0, BtnExit.rect_size.y)
  ConsoleTxt.rect_size = Vector2(consoleRes.x, consoleRes.y-BtnExit.rect_size.y-TxtEdit.rect_size.y)
Nun noch einige Funktionen für die Bedienung der Konsole:
# show the console and set the focus on the text edit.
func show()->void:
  self.visible = true
  TxtEdit.grab_focus()
  emit_signal("console_shown")
  get_tree().paused = true

# hide the console
func hide()->void:
  get_tree().paused=false()
  self.visible=false
  emit_signal("console_hidden")
4. Nun werden die Signale verknüpft. Doppelklicke rechts im Inspektor auf das signal "pressed" vom Button "BtnExit" und wähle das Panel "Console" mit dem Script als Ziel. Dort erscheint nun eine Funktion, in welche du einfach nur hide() reinschreibst:
# event when the exit button was pressed
func _on_BtnExit_pressed():
  hide()
Dasselbe machen wir mit dem Clear-Button:
# event when the clear button was pressed
func _on_BtnClear_pressed():
  ConsoleTxt.text=""
  TxtEdit.grab_focus() # get the focus back to the lineedit.
Für den Konsole-Text brauchen wir eine Funktion, die den Fokus immer wieder auf das LineEdit "EditText" lenkt. Ansonsten könnte man in den Text reinschreiben und das wollen wir nicht. Mit dem Readonly-Flag wird der Text grau und das wollen wir auch nicht. Doppelklicke also auf das "focus_entered()"-Signal des ConsoleText-Nodes (TextEdit) und verknüpfe es wie oben beschrieben. Hier muss man nur TxtEdit.grab_focus() reinschreiben:
# grab the focus on the line edit
# when someone clicks on the console text.
func _on_ConsoleText_focus_entered():
  TxtEdit.grab_focus()
Und schliesslich brauchen wir noch die Funktion, wenn der Benutzer im LineEdit die Enter-Taste drückt. Das Signal heisst "text_entered" im LineEdit-Node.
# enter was pressed on the line edit.
func _on_EditText_text_entered(new_text):
  var txt = TxtEdit.text
  add_line("> "+txt)
  parse(txt)
  TxtEdit.clear()
Mit add_line wird der Text an den Konsolentext angehängt und mit einem \n beendet. Jede Zeile muss also ein \n am Ende haben, sonst funktioniert das Ganze nicht richtig.
func add_line(txt:String)->void:
  print(txt) # gib den text im debugger aus.
  ConsoleTxt.text += txt
  ConsoleTxt.text += "\n"
  # scroll vertical to infinity = to end of text
  ConsoleTxt.scroll_vertical = INF
Um den Text mit dem Programm zu verknüpfen haben wir die parse-Funktion. Hier werden ein paar interne Kommandos ausgewertet und dann das Signal "console_command_given" emittiert. Dieses Signal kann abgefangen und der Text ausgewertet werden. Als Beispielfunktion gibt es hier einfach "exit" um die Konsole zu beenden.
func parse(Txt:String)->void:   Txt = Txt.to_lower() # text auf lowercase.
  if Txt=="":
    return # nothing happens if nothing gets in.   if Txt=="exit":
    hide()
    return
  # command not recognized (no return before)
  emit_signal("console_command_given", Txt, self)
Für komplexere Befehle muss ich noch ein bisschen recherchieren. ;)

Das Signal hat als Parameter den Text und die Konsole selbst. Man kann also einfach eine Funktion in einem anderen Node mit dem Signal verknüpfen und zum Beispiel Cheats einbauen:
# ein cheat für den player
func on_console_command_given(var Txt, var Console)
  if Txt="heileheilesegen":
    health=max_health
      Console.add_line("CHEAT ACTIVATED")

Hiermit hast du eine funktionsfähige Konsole, welche du einfach an deine Mainscene anhängen kannst. Achte darauf, dass sie zu unterst im Nodetree ist, damit sie immer im Vordergrund ist. Setze das visible-flag auf false und erstelle einen Button oder anderen Event mit Console.show() als Funktionscode. Das sollte erstmal reichen. ;)

Freitag, 7. Februar 2020

Mobile Arcade (Race Version)

Bei einem Kollegen fand ich 2018 ein Logitech G29 mit Metallrahmen, Schalensitz und allem möglichen anderem Krempel. Dieses musste ich ihm sofort abkaufen, was ich dann auch tat - mit einem Ratenvertrag. Innerhalb eines halben Jahres hatte ich die vereinbarten 777.- abbezahlt.

Dann habe ich das Teil in meiner Garage installiert. Ich bekam einen Computer und konnte es erstmal ausgiebig testen. Dirt. Der Nachfolger von Colin McRae Rally. Denn Rally ist das Einzige, wofür meiner Meinung nach Autos entwickelt wurden. Für alles andere gibt es UFOs. Naja, noch nicht. Aber das ist eine andere Story.



Der Ex-Besitzer testet die Konfiguration.
Ich bekam dann noch ein Transportgefährt (Platte mit vier Rädern), worauf ich mit einem einzigen Seil(!) den Metallrahmen und die Steckerleiste installiert habe.



Von meinem Grossvater habe ich einen kleinen Gartentisch "geerbt", welcher genau dieselbe Breite wie das Gestell hat. Ich konnte den Tisch einfach reinstecken.

Seil komplett umwickelt, Porsche siehe unten.
Auf der Strasse zu meiner Wohnung habe ich dann den LEGO 42056 Porsche GTR3 gefunden, welcher mit Gratis angeschrieben war. Dort passt der Computer perfekt rein, dachte ich, und habe dafür das Getriebe ausgebaut.

Das einzige übrige Foto vom "Original" (mit Computer)
Leider war der Porsche dann so instabil, dass ich ihn nach einem Zerstörungs-Wut-Tritt und dem Aufsammeln danach komplett neu aufgebaut habe.

Einige Teile sind vom Tritt noch ganz geblieben. Ich habe diese genommen und auf dieser Basis einen Rahmen um den PC gebaut. Zum Glück waren beide Radaufhängungen vorn und hinten noch komplett, sowie die Türen und Teile des vorderen und hinteren Aufbaus.




Der Vorderteil war noch relativ komplett.
An den Stangen hochklappen...
Unterseite
Komplettes Fahrgestell
Nachdem der PC stabil in der Karre eingebaut war, musste die Karosserie wieder darum herum aufgebaut werden. Diese war anfangs ziemlich lose und wurde erst stabil, als schliesslich das Dach dran kam. Hier ist das Endprodukt. Ein Teil fehlte, weshalb ich es symmetrisch durch die zwei grünen ausgetauscht habe.

Die Karosserie war auch hier lange sehr instabil, so dass ich die Hinterräder wegklappen konnte um die Kabel einzustecken. Erst als diese richtig drinne waren, inklusive Netzwerkkabel (DANKE SEHR, wenn ich das nicht eingebaut hätte, wäre ich  nun aufgeschmissen), habe ich die Karosserie darum richtig fest gemacht. Das Netzwerkkabel wird schliesslich im Heck verstaut, da es beim Betrieb der Arcade-Station nicht gebraucht wird.

Das Heck oben ist komplett neu.
Die Kabel passen und sind sogar fixiert.
von unten
von hinten
von vorn
von oben
von der Seite
von wenn er dich überholt :) (in England)
Die Türen sind leider einen Pin tiefer als vorher, doch das Endergebnis gefällt mir sogar noch besser als das Original. Durch die tiefen Türen sieht er noch ein bisschen schneller aus. :)

Leider geht die Steuerung nicht mehr und es hat auch keine Sitze mehr. Doch der Porsche ist nun so stabil, dass man ihn hinten am Dach aufheben kann, inklusive der etwa 2Kg Gewicht des Computers, und er sogar noch Bodenfreiheit hat. (Trick: Zwei der hinteren Federn waren kaputt, da hab ich einfach zwei 7er-Lochstäbe eingebaut.)

Stabiler als das Original. Viel stabiler.

Mit Bodenfreiheit
Den Computer kann man anschalten, indem man beim Linken Vorderrad (von vorn gesehen) den Finger dazwischen klemmt und nach hinten drückt. Sorry, eine bessere Variante habe ich noch nicht herausgefunden.



Dazu habe ich dann noch eine Halterung gebaut, welche auf dem Tischchen mit Kabelbindern fixiert wurde. So kann man den Computer-Porsche leicht abnehmen für Konfigurationen und weiteres.
Weil, wenn Steam online gehen will, dann hat man ansonsten ein Problem. Das kann ja mal passieren, dass man das alles falsch beendet (bzw. Steam beendet bevor man herunterfährt) und dann will Steam beim nächsten Login unbedingt wieder online gehen...

Merke: Bei Steam Offline gehen, und dann NIE WIEDER STEAM BEENDEN! Auch keinen Neustart sondern immer schön herunterfahren und kalt neu starten. (Kalt heisst: Du musst aufs Knöpfchen drücken, damit der PC wieder Strom bekommt.)


Und schliesslich habe ich noch einen Beamer und ein Stativ dazu gelegt. Als Leinwand benutze ich die Rückseite eines alten Filmposters aus Latex?, welches ich aus dem Kino habe.

[Der Aufbau des Beamers und der Leinwand dauert jeweils zu lange, also...]

Schliesslich habe ich einen Campinghocker genommen, bei welchem der Stoff schon angerissen war, diesen Stoff weg gemacht, ein Brett daran, und ihn dann liegend zwischen Steuerrad und Porsche fixiert. Daran habe ich meinen Bildschirm befestigt. Und fertig...soweit.

Hier sind die Fotos des aufgebauten Teils, in der Garage (später noch draussen.):



Und draussen kommts dann voll zur Geltung:



Es macht soooooo Spass mit dem Ding zu fahren, ich hoff mal, "man" darf nextens auch wieder mal richtig raus aus dem "globalen Knast" (siehe dazu einen beliebigen Zeitungsartikel von Heute, Gestern, Morgen (15-17.03.2020)) damit das Ding auch wer anders als ich benutzen darf. :)

Montag, 18. November 2019

Unity: NavMesh-Debugging

Um ein Waypoint-System zu bauen habe ich mich informiert, wie man eine Linie in Unity zeichnen kann. Das kann man auch NUR IM EDITOR, was sehr hilfreich ist.

Dazu gibt es die Funktionen "OnDrawGizmos" und "OnDrawGizmosSelected".

Hier ist der Code, welcher eine Linie vom aktuellen zum nächsten NavMesh-zieht.

Erstelle dieses Script, hänge es an ein GameObject ran und ziehe dir ein Prefab davon:

public class NavCon
{
   public GameObject nextWaypoint;
   protected Color actualColor = Color.blue;

   private void OnDrawGizmos()
   {
        if(nextWaypoint)
        {
            Gizmos.color = actualColor;
            Gizmos.DrawLine(gameObject.transform.position, nextWaypoint.transform.position, actualColor);
        }
        // reset the color.
        actualColor = Color.blue
   }

   private void OnDrawGizmosSelected() {actualColor = Color.green;}
}


Wenn du nun ein anderes GameObject auf das "nextWaypoint"-Feld im Inspector ziehst, wird im Editor eine blaue oder grüne Linie zwischen den beiden GameObjects gezeichnet. Da es keine "OnDrawGizmosUnselected"-Funktion gibt, muss die Farbe nach jedem Zeichnen ge-resetted werden.