Search code examples
c#lualuainterfacenlua

NLua - How to add new functions to a userdata through Lua?


I'm trying to define a new function for my Player class which was defined in C#. The purpose of Lua in my project (a game engine) is to define custom behavior for entities such as players.

However, when I do DoFile(fileName) on the Lua file, it crashes with this exception:

"field or property 'Idle' does not exist"

It specifically points to the first line in this chunk of code:

function Player:Idle()
    self:Turn()
    self.StateType = StateType.Stand
    self.MoveType = MoveType.Idle
    self.Vel = Vector2.Zero
    if self.Anim.Number ~= 0 and (self.Anim.Number ~= 5 or self:GetAnimTime() == 0) then
        self:ChangeAnim(0)
    end
end

It seems to have a problem with writing Player:Idle. However, I also tried writing it as Player.Idle and I get the same issue. Here's how I handle loading the scripts for the player in C#:

        state["Player"] = this;

        // Load the states
        for (int j = 0; j < stateFiles.Length; j++)
        {
            string fp = filePath + @"\" + stateFiles[j];

            // If it's common, then if it doesn't exist, use the default.
            if (stateFiles[j] == "common.lua" && !File.Exists(fp))
                fp = Path.Combine("Content", "common.lua");

            // Now load it
            var f = state.DoFile(fp);
        }

I'm setting the Player global to this because this is the player class, so any new functions need to be declared in the context of Player. What am I doing wrong?


Edit

I've solved the error from earlier, but I'm still not getting the results I want. It seems it's not possible to directly do this; however I've read up and it seems in Lua 5.2, there's a way to do something like this by associating a table with the userdata object through debug.setuservalue. However, when I tried using it, the table was still empty (even though the userdata was not).

This is what I tried (C#):

        state.NewTable("Player");
        state["tmp"] = this;
        state.DoString("a = debug.setuservalue(tmp, Player)");
        var t = state["Player"]; // Shows that Player is an empty table
        var a = state["a"]; // Shows that a was indeed set.

In other words, I want to be able to use self in the script to be able to refer to the userdata, as well as custom functions, such as Idle() and Turn().

How can I achieve the behavior I desire?


Solution

  • From the Lua reference manual: https://www.lua.org/manual/5.3/manual.html#2.1

    Userdata values cannot be created or modified in Lua, only through the C API. This guarantees the integrity of data owned by the host program.

    So you cannot add functions to userdata but you write your own, extended interface by wrapping the userdata in your own Lua table.

    Simple example, assuming you can create a Player userdata value by calling Player().

    WrappedPlayer = {}
    WrappedPlayer.usrdata = Player()
    function WrappedPlayer:Idle()
      self.usrdata:Turn()
      self.usrdata.MoveType = MoveType.Idle
    end
    

    Then you can simply call WrappedPlayer:Idle()

    Of course you can further improve this and after overwriting the Player table with your own and adding some metatable magic you could simply create your very own Player object:

    local myPlayer = Player()
    myPlayer:Idle()
    

    Just read some Lua OOP tutorials to find out how.

    Not to forget a very simple solution:

    function SetPlayerIdle(player)
      player:Turn()
      -- and so forth
    end