13 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
- Attachments
- Your Turn
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 billig 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 diese Entity erstellt, die 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 den give privilege 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)
Punch, Damage Groups, and Armor Groups
Damage is the reduction of an object's HP. An object can punch another object to inflict damage. A punch isn't necessarily an actual punch - it can be an explosion, a sword slash, or something else.
The total damage is calculated by multiplying the punch's damage groups with the target's vulnerabilities. This is then limited depending on how recent the last punch was. We will go over an example of this calculation in a bit.
Just like node dig groups, these groups can take any name and do not need to be registered. However, it's common to use the same group names as with node digging.
How vulnerable an object is to particular types of damage depends on its
armor_groups
object property. Despite its misleading
name, armor_groups
specify the percentage damage taken from particular damage
groups, not the resistance. If a damage group is not listed in an object's armor
groups, that object is completely invulnerable to it.
target:set_properties({
armor_groups = { fleshy = 90, crumbly = 50 },
})
In the above example, the object will take 90% of fleshy
damage and 50% of
crumbly
damage.
When a player punches an object, the damage groups come from the item they are currently wielding. In other cases, mods decide which damage groups are used.
Example Damage Calculation
Let's punch the target
object:
local tool_capabilities = {
full_punch_interval = 0.8,
damage_groups = { fleshy = 5, choppy = 10 },
-- This is only used for digging nodes, but is still required
max_drop_level=1,
groupcaps={
fleshy={times={[1]=2.5, [2]=1.20, [3]=0.35}, uses=30, maxlevel=2},
},
}
local time_since_last_punch = tool_capabilities.full_punch_interval
target:punch(object, time_since_last_punch, tool_capabilities)
Now, let's work out what the damage will be. The punch's damage groups are
fleshy=5
and choppy=10
, and target
will take 90% damage from fleshy and 0%
from choppy.
First, we multiply the damage groups by the vulnerability and sum the result.
We then multiply by a number between 0 or 1 depending on the time_since_last_punch
.
= (5*90/100 + 10*0/100) * limit(time_since_last_punch / full_punch_interval, 0, 1)
= (5*90/100 + 10*0/100) * 1
= 4.5
As HP is an integer, the damage is rounded to 5 points.
Attachments
Attached objects will move when the parent - the object they are attached to - is moved. An attached object is said to be a child of the parent. An object can have an unlimited number of children, but at most one parent.
child:set_attach(parent, bone, position, rotation)
An object's get_pos()
will always return the global position of the object, no
matter whether it is attached or not.
set_attach
takes a relative position, but not as you'd expect.
The attachment position is relative to the parent's origin as scaled up by 10 times.
So, 0,5,0
would be half a node above the parent's origin.
{% include notice.html notice=page.degrad %}
For 3D models with animations, the bone argument is used to attach the entity to a bone. 3D animations are based on skeletons - a network of bones in the model where each bone can be given a position and rotation to change the model, for example, to move the arm. Attaching to a bone is useful if you want to make a character hold something:
obj:set_attach(player,
"Arm_Right", -- default bone
{x=0.2, y=6.5, z=3}, -- default position
{x=-100, y=225, z=90}) -- default rotation
Your Turn
- Make a windmill by combining nodes and an entity.
- Make a mob of your choice (using just the entity API, and without using any other mods).