minetest_modding_book/_de/map/objects.md
2022-08-22 17:14:18 +02:00

13 KiB

title: Objekte, Spieler, und Entities layout: default root: ../.. idx: 3.4 description: Nutzung eines ObjectRef degrad: level: warning title: Grad and Radiant message: Die Drehung von Anbauteilen wird in Grad angegeben, während die Drehung von Objekten in Radiant angegeben wird. Stellen Sie sicher, dass Sie das richtige Winkelmaß verwenden.

Einleitung

In diesem Kapitel lernen Sie, wie man Objekte manipuliert und eigene Objekte definiert.

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).