14 KiB
Einleitung
In diesem Kapitel lernen Sie, wie man Objekte manipuliert und eigene Objekte definiert.
- Was sind Objekte, Spieler, und Entities?
- Position und Geschwindigkeit
- Objekt-Eigenschaften
- Entities
- Leben und Schaden
- Anhänge
- Sie sind dran
Was sind Objekte, Spieler, und Entities?
Spieler und Entities sind beide Arten von Objekten. Ein Objekt ist etwas, das sich unabhängig vom Block-Raster bewegen kann und Eigenschaften wie Geschwindigkeit und Skalierung besitzt. Objekte sind keine Gegenstände, und sie haben ihr eigenes Registrierungssystem.
Es gibt ein paar Unterschiede zwischen Spielern und Entities. Der größte ist, dass Spieler von Spielern gesteuert werden, während Entities von Mods gesteuert werden. Das bedeutet, dass die Geschwindigkeit eines Spielers nicht von Mods eingestellt werden kann - Spieler sind client-seitig, und Entities sind serverseitig. Ein weiterer Unterschied ist, dass Spieler das Laden von Kartenblöcken verursachen, während Entities nur gespeichert werden und inaktiv werden.
Diese Unterscheidung wird durch die Tatsache erschwert, dass Entities über eine Tabelle gesteuert werden, die als Lua entity bezeichnet wird, wie später erläutert wird.
Position und Geschwindigkeit
get_pos
und set_pos
existieren, um die Position eines Entitys zu ermitteln und zu setzen.
local objekt = minetest.get_player_by_name("bob")
local pos = objekt:get_pos()
objekt:set_pos({ x = pos.x, y = pos.y + 1, z = pos.z })
set_pos
setzt die Position sofort und ohne Animation. Wenn Sie ein Objekt
sanft an die neue Position animieren möchten, sollte man move_to
verwenden.
Dies funktioniert leider nur für Entities.
objekt:move_to({ x = pos.x, y = pos.y + 1, z = pos.z })
Ein wichtiger Punkt beim Umgang mit Entities ist die Latenzzeit im Netz.
In einer idealen Welt würden Nachrichten über Entitybewegungen sofort ankommen,
in der richtigen Reihenfolge und in einem ähnlichen Intervall ankommen, wie Sie sie gesendet haben.
Solange man sich jedoch nicht im Einzelspielermodus befindet, ist dies keine ideale Welt.
Nachrichten brauchen eine Weile, bis sie ankommen. Positionsnachrichten können in der falschen Reihenfolge ankommen,
was dazu führt, dass einige set_pos
-Aufrufe übersprungen werden, da es keinen Sinn macht, zu einer
Position zu gehen, die älter ist als die aktuell bekannte Position.
Bewegungen können nicht in ähnlichen Abständen erfolgen, was es schwierig macht, sie für Animationen zu verwenden.
All dies führt dazu, dass der Client andere Dinge sieht als der Server, und das ist etwas,
das Sie beachten müssen.
Objekt-Eigenschaften
Objekt-Eigenschaften werden verwendet, um dem Client mitzuteilen, wie ein Objekt zu rendern und zu behandeln ist. Es ist nicht möglich, benutzerdefinierte Eigenschaften zu definieren, denn die Eigenschaften sind per Definition von der Engine zu verwenden.
Im Gegensatz zu Blöcken haben Objekte ein dynamisches und kein festes Aussehen. Sie können unter anderem das Aussehen eines Objekts jederzeit ändern, indem Sie seine Eigenschaften ändern.
object:set_properties({
visual = "mesh",
mesh = "character.b3d",
textures = {"character_texture.png"},
visual_size = {x=1, y=1},
})
Die aktualisierten Eigenschaften werden an alle Spieler in Reichweite gesendet. Dies ist sehr nützlich, um eine große Menge an Vielfalt sehr einfach zu bekommen, wie zum Beispiel verschiedene Skins pro Spieler.
Wie im nächsten Abschnitt gezeigt wird, können Entities Erst-Eigenschaften haben,
die in ihrer Definition angegeben werden.
Die Standardeigenschaften des Spielers sind jedoch in der Engine definiert, so dass man
on_joinplayer
die Funktion set_properties()
verwenden kann, um die Eigenschaften für neue
Spieler zu setzen.
Entities
Ein Entity hat eine Definitionstabelle, die einer Objektdefinitionstabelle ähnelt. Diese Tabelle kann Callback-Methoden, anfängliche Objekteigenschaften und benutzerdefinierte Mitglieder enthalten.
local MeinEntity = {
initial_properties = {
hp_max = 1,
physical = true,
collide_with_objects = false,
collisionbox = {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3},
visual = "wielditem",
visual_size = {x = 0.4, y = 0.4},
textures = {""},
spritediv = {x = 1, y = 1},
initial_sprite_basepos = {x = 0, y = 0},
},
message = "Default message",
}
function MeinEntity:set_message(msg)
self.message = msg
end
Entity-Definitionen unterscheiden sich in einem sehr wichtigen Punkt von Item-Definitionen. Wenn ein Entity auftaucht (d.h.: geladen oder erstellt wird), wird eine neue Tabelle für dieses Entity erstellt, die es von der Definitionstabelle erbt.
Sowohl ein ObjectRef als auch eine Entity-Tabelle bieten Möglichkeiten, das Gegenstück zu erhalten:
local entity = object:get_luaentity()
local objekt = entity.objekt
print("Entity ist bei " .. minetest.pos_to_string(objekt:get_pos()))
Es gibt eine Reihe von Callbacks für die Verwendung mit Entities. Eine vollständige Liste findet sich in lua_api.txt 🇬🇧.
function MeinEntity:on_step(dtime)
local pos = self.object:get_pos()
local pos_drunter = vector.subtract(pos, vector.new(0, 1, 0))
local delta
if minetest.get_node(pos_drunter).name == "air" then
delta = vector.new(0, -1, 0)
elseif minetest.get_node(pos).name == "air" then
delta = vector.new(0, 0, 1)
else
delta = vector.new(0, 1, 0)
end
delta = vector.multiply(delta, dtime)
self.object:move_to(vector.add(pos, delta))
end
function MeinEntity:on_punch(hitter)
minetest.chat_send_player(hitter:get_player_name(), self.message)
end
Wenn Sie nun diese Entity spawnen und verwenden würden, würden Sie feststellen, dass die Nachricht vergessen wird, wenn die Entity inaktiv und dann wieder aktiv wird. Das liegt daran, dass die Nachricht nicht gespeichert wird. Anstatt alles in der Entity-Tabelle zu speichern, gibt Minetest Ihnen die Kontrolle darüber wie die Dinge gespeichert werden sollen. Staticdata ist ein String, der alle benutzerdefinierten Informationen enthält, die gespeichert werden müssen.
function MeinEntity:get_staticdata()
return minetest.write_json({
message = self.message,
})
end
function MeinEntity:on_activate(staticdata, dtime_s)
if staticdata ~= "" and staticdata ~= nil then
local data = minetest.parse_json(staticdata) or {}
self:set_message(data.message)
end
end
Minetest kann get_staticdata()
so oft wie gewünscht und zu jeder Zeit aufrufen.
Der Grund dafür ist, dass Minetest nicht darauf wartet, dass ein MapBlock inaktiv wird,
um ihn zu speichern, da dies zu Datenverlusten führen würde. MapBlocks werden ungefähr alle 18
Sekunden gespeichert, also sollten Sie ein ähnliches Intervall für den Aufruf von get_staticdata()
feststellen.
on_activate()
wird dagegen nur aufgerufen, wenn eine Entity
aktiv wird, entweder wenn der MapBlock aktiv wird oder wenn die Entity spawnen wird.
Dies bedeutet, dass staticdata leer sein könnte.
Schließlich müssen Sie die Typentabelle mit der treffenden Bezeichnung register_entity
registrieren.
minetest.register_entity("meinemod:entity", MeinEntity)
Die Entity kann von einem Mod wie folgt erzeugt werden:
local pos = { x = 1, y = 2, z = 3 }
local obj = minetest.add_entity(pos, "meinemod:entity", nil)
Der dritte Parameter sind die anfänglichen statischen Daten. Um die Nachricht einzustellen, können Sie die Methode der Entity-Tabelle verwenden:
obj:get_luaentity():set_message("hello!")
Spieler mit dem give Privileg können einen Chat command zum spawnen von entities benutzen:
/spawnentity mymod:entity
Leben und Schaden
Lebenspunkte (HP)
Jedes Objekt hat eine Anzahl von Lebenspunkten (HP), die die aktuelle Gesundheit darstellt.
Spieler haben eine maximale Lebenspunktzahl, die mit der Objekteigenschaft hp_max
festgelegt wird.
Ein Objekt stirbt, wenn seine HP 0 erreichen.
local hp = object:get_hp()
object:set_hp(hp + 3)
Schlagen, Damage Groups, und Armor Groups
Schaden ist die Verringerung der HP eines Objekts. Ein Objekt kann ein anderes Objekt schlagen, um Schaden zuzufügen. Ein Schlag muss nicht unbedingt ein echter Schlag sein - es kann auch eine Explosion, ein Schwerthieb oder etwas anderes sein.
Der Gesamtschaden wird durch Multiplikation der Schadensgruppen des Schlags mit den Verwundbarkeiten des Ziels berechnet. Dies wird dann begrenzt, je nachdem, wie lange der letzte Schlag her war. Wir werden gleich ein Beispiel für diese Berechnung erläutern.
Genau wie Node Grabungsgruppen, können diese Gruppen jeden Namen annehmen und müssen nicht registriert werden. Es ist jedoch üblich, dieselben Gruppennamen wie bei Node digging zu verwenden.
Wie anfällig ein Objekt für bestimmte Arten von Schaden ist, hängt von seiner
armor_groups
Objekteigenschaft ab. Trotz seines irreführenden
Namen gibt armor_groups
den prozentualen Schaden von bestimmten
Schadensgruppen an,und nicht den Widerstand. Wenn eine Schadensgruppe nicht in den Armor Groups eines Objekts aufgeführt ist,
ist das Objekt völlig unverwundbar.
target:set_properties({
armor_groups = { fleshy = 90, crumbly = 50 },
})
Im obigen Beispiel erleidet das Objekt 90 % fleshy
Schadens und 50 % des
crumbly
-Schaden.
Wenn ein Spieler ein Objekt schlägt, stammen die Schadensgruppen von dem Gegenstand, das er gerade trägt. In anderen Fällen entscheiden die Mods, welche Schadensgruppen verwendet werden.
Beispiel für die Schadensberechnung
Schlagen wir das Objekt target
:
local werkzeug_faehigkeiten = {
full_punch_interval = 0.8,
damage_groups = { fleshy = 5, choppy = 10 },
-- Dies wird nur für das abbauen von Nodes verwendet, ist aber dennoch erforderlich
max_drop_level=1,
groupcaps={
fleshy={times={[1]=2.5, [2]=1.20, [3]=0.35}, uses=30, maxlevel=2},
},
}
local zeit_seit_letzten_schlag = werkzeug_faehigkeiten.full_punch_interval
target:punch(object, zeit_seit_letzten_schlag, werkzeug_faehigkeiten)
Berechnen wir nun, wie hoch der Schaden sein wird. Die Schadensgruppen des Schlags sind
fleshy=5
und choppy=10
, und die Zielperson erleidet 90% Schaden durch fleshy und 0%
von choppy.
Zuerst multiplizieren wir die Schadensgruppen mit der Verwundbarkeit und addieren das Ergebnis.
Dann multiplizieren wir mit einer Zahl zwischen 0 oder 1, abhängig von der zeit_seit_letzten_schlag
.
= (5*90/100 + 10*0/100) * limit(zeit_seit_letzten_schlag / full_punch_interval, 0, 1)
= (5*90/100 + 10*0/100) * 1
= 4.5
Da die HP eine ganze Zahl sind, wird der Schaden auf 5 Lebenspunkte gerundet.
Anhänge
Angehängte Objekte bewegen sich, wenn das übergeordnete Objekt - also das Objekt, an das sie angehängt sind - bewegt wird. Ein angefügtes Objekt ist ein Kind des übergeordneten Objekts. Ein Objekt kann eine unbegrenzte Anzahl von Kindern haben, aber höchstens ein Elternteil.
child:set_attach(parrent, knochen, position, drehung)
Die Funktion get_pos()
eines Objekts gibt immer die globale Position des Objekts zurück,
unabhängig davon, ob es angehängt ist oder nicht.
set_attach
nimmt eine relative Position, aber nicht so, wie man es erwarten würde.
Die Anhängeposition ist relativ zum Ursprung des Elternobjekts und wird um das 10-fache vergrößert.
Also wäre "0,5,0" ein halber Node über dem Ursprung des Elternobjekts.
{% include notice.html notice=page.degrad %}
Bei 3D-Modellen mit Animationen wird das Argument knochen verwendet, um das Entity an einen Knochen zu binden. 3D-Animationen basieren auf Skeletten - einem Netzwerk von Knochen im Modell, bei dem jedem Knochen eine Position und Drehung zugewiesen werden kann, um das Modell zu verändern, z. B, um den Arm zu bewegen. Das Anhängen an einen Knochen ist nützlich, wenn Sie eine Figur etwas halten lassen wollen:
obj:set_attach(spieler,
"Arm_Right", -- normaler Knochen
{x=0.2, y=6.5, z=3}, -- normale Position
{x=-100, y=225, z=90}) -- normale Drehung
Sie sind dran
- Erstelle eine Windmühle, indem du Nodes und eine Entity kombinierst.
- Erstelle einen Mob deiner Wahl (nur mit der Entity-API und ohne andere Mods zu verwenden).