LoginSignup
3
5

More than 5 years have passed since last update.

c++プラグインでSquirrelスクリプトとやり取りをする(Android)

Last updated at Posted at 2017-07-11

環境

Unity5.6.2f1
Squirrel 3.1

概要

c++Likeなスクリプト言語のSquirrelとやり取りをするテストです

Squirrelのダウンロード

以下のページで「Download」を押す
https://sourceforge.net/projects/squirrel/

AndroidStudioとUnityの事前設定

以下を参照してプロジェクトを作成する
プラグインでc++と連携する(Android)
libunisquirrel.soとしてエクスポートする

Squirrel関連のファイル追加

・AndroidのNativePluginを作成した時の「cpp」フォルダ内に「squirrel3」というフォルダを作成
・ダウンロードしたtar.gzを展開する
・「squirrel」「sqstdlib」「include」フォルダを作成した「squirrel3」の中にフォルダ毎コピーする
・コピーしたフォルダ内の「.cpp」と「.h」以外を削除する

CMakeLists.txt

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             UniSquirrel.cpp
             #squirrel3
             squirrel3/squirrel/sqapi.cpp
             squirrel3/squirrel/sqbaselib.cpp
             squirrel3/squirrel/sqclass.cpp
             squirrel3/squirrel/sqcompiler.cpp
             squirrel3/squirrel/sqdebug.cpp
             squirrel3/squirrel/sqfuncstate.cpp
             squirrel3/squirrel/sqlexer.cpp
             squirrel3/squirrel/sqmem.cpp
             squirrel3/squirrel/sqobject.cpp
             squirrel3/squirrel/sqstate.cpp
             squirrel3/squirrel/sqtable.cpp
             squirrel3/squirrel/sqvm.cpp
             squirrel3/sqstdlib/sqstdaux.cpp
             squirrel3/sqstdlib/sqstdblob.cpp
             squirrel3/sqstdlib/sqstdio.cpp
             squirrel3/sqstdlib/sqstdmath.cpp
             squirrel3/sqstdlib/sqstdrex.cpp
             squirrel3/sqstdlib/sqstdstream.cpp
             squirrel3/sqstdlib/sqstdstring.cpp
             squirrel3/sqstdlib/sqstdsystem.cpp

)

# Specifies a path to native header files.
include_directories( Unity/ )
include_directories( squirrel3/include/ )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log
)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
                       native-lib
                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib}
)

プログラム

Squirrelの「.nut」ファイルはUTF8(bom無し)CR+LFでテストしました

UniSquirrel.cpp

#include "IUnityInterface.h"
#include <math.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
//#define SQUNICODE
#include "squirrel.h"
#include "sqstdio.h"
#include "sqstdaux.h"

static void printfunc( HSQUIRRELVM v, const SQChar* pFormat, ... );
static SQInteger print_args( HSQUIRRELVM v );
static SQInteger register_global_func( HSQUIRRELVM v, SQFUNCTION f, const char *fname );
static void CallSquirrelFunc( HSQUIRRELVM v );

extern "C"
{
    using CallbackOutputString = void(*)( const SQChar* );
    namespace
    {
        CallbackOutputString onCallbackOutputString = NULL;
    }

    UNITY_INTERFACE_EXPORT void SetCallbackOutputString( CallbackOutputString func )
    {
        onCallbackOutputString = func;
    }

    UNITY_INTERFACE_EXPORT void ExecuteScript( const SQChar* pPath )
    {
        if ( onCallbackOutputString == NULL )
            return;

        HSQUIRRELVM v;
        v = sq_open( 1024 );
        sqstd_seterrorhandlers( v );
        sq_setprintfunc( v, printfunc, printfunc );
        sq_pushroottable( v );
        //c/c++関数の登録
        register_global_func( v, print_args, "print_args" );

        if( SQ_SUCCEEDED( sqstd_dofile( v, pPath, 0, 1 ) ) )
        {
            printf("Call Failed!");
        }

        //squirrel関数の呼び出し
        CallSquirrelFunc( v );

        sq_close(v);
    }

    UNITY_INTERFACE_EXPORT void ExecuteScriptInMemory( const SQChar* pBuffer )
    {
        if ( onCallbackOutputString == NULL )
            return;

        HSQUIRRELVM v;
        v = sq_open( 1024 );
        sqstd_seterrorhandlers( v );
        sq_setprintfunc( v, printfunc, printfunc );
        sq_pushroottable( v );

        sq_compilebuffer( v, pBuffer,(int)strlen( pBuffer ) * sizeof( SQChar ), "compile", 1 );
        sq_pushroottable(v);
        sq_call(v,1,1,0);

        sq_close(v);
    }

    static void CallSquirrelFunc( HSQUIRRELVM v )
    {
        //SQInteger saveStack = sq_gettop( v );
        sq_pushroottable( v );
        sq_pushstring( v, _SC( "TestFunc" ), -1 );
        if( SQ_SUCCEEDED( sq_get( v, -2 ) ) )
        {
        }
        int n = 123;
        float f = 345.67f;
        const SQChar* s = _SC( "abcd" );
        sq_pushroottable( v );
        sq_pushinteger( v, n );
        sq_pushfloat( v, f );
        sq_pushstring( v, s, -1 );
        sq_call( v, 4, SQTrue, 0 );

        if( sq_gettype( v, -1 ) == OT_INTEGER )
        {
            SQInteger ret;
            sq_getinteger( v, -1, &ret );
            sq_pop( v, 1 );
        //    sq_settop( v, saveStack );
            char str[64] = "";
            snprintf( str, sizeof( str ) - 1, "%d", ret );
            onCallbackOutputString( str );
        }
    }

    static void printfunc( HSQUIRRELVM v, const SQChar* pFormat, ... )
    {
        va_list arglist;
        va_start( arglist, pFormat );
        SQChar buffer[ 1024 ] = _SC( "" );
        vsnprintf( buffer, sizeof(buffer), pFormat, arglist );
        va_end( arglist );

        onCallbackOutputString( buffer );
    }

    static SQInteger print_args( HSQUIRRELVM v )
    {
        SQInteger nargs = sq_gettop(v); //number of arguments
        for(SQInteger n=1;n<=nargs;n++)
        {
            printf("arg %d is ",n);
            switch(sq_gettype(v,n))
            {
                case OT_NULL:
                    onCallbackOutputString("null");
                    break;
                case OT_INTEGER:
                    onCallbackOutputString("integer");
                    break;
                case OT_FLOAT:
                    onCallbackOutputString("float");
                    break;
                case OT_STRING:
                    onCallbackOutputString("string");
                    break;
                case OT_TABLE:
                    onCallbackOutputString("table");
                    break;
                case OT_ARRAY:
                    onCallbackOutputString("array");
                    break;
                case OT_USERDATA:
                    onCallbackOutputString("userdata");
                    break;
                case OT_CLOSURE:
                    onCallbackOutputString("closure(function)");
                    break;
                case OT_NATIVECLOSURE:
                    onCallbackOutputString("native closure(C function)");
                    break;
                case OT_GENERATOR:
                    onCallbackOutputString("generator");
                    break;
                case OT_USERPOINTER:
                    onCallbackOutputString("userpointer");
                    break;
                default:
                    return sq_throwerror(v,"invalid param"); //throws an exception
            }
            onCallbackOutputString("\n");
        }
        sq_pushinteger(v,nargs); //push the number of arguments as return value
        return 1; //1 because 1 value is returned
    }

    static SQInteger register_global_func( HSQUIRRELVM v, SQFUNCTION f, const SQChar* fname )
    {
        sq_pushroottable(v);
        sq_pushstring(v,fname,-1);
        sq_newclosure(v,f,0); //create a new function
        sq_createslot(v,-3);
        sq_pop(v,1); //pops the root table
        return 0;
    }
}

/Assets/TestSquirrel.cs

using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using System.Runtime.InteropServices;
using System.IO;
using System.Collections;

public class TestSquirrel : MonoBehaviour 
{
    [SerializeField] Text _text = null;

    [DllImport ("unisquirrel")]
    public static extern void SetCallbackOutputString( UnityAction<string> func );
    [DllImport ("unisquirrel")]
    public static extern void ExecuteScript( string path );
    [DllImport ("unisquirrel")]
    public static extern void ExecuteScriptInMemory( string buffer );

    private static TestSquirrel _instance = null;
    private void Awake()
    {
        _instance = this;
    }

    private void OnDestroy()
    {
        _instance = null;
    }

    private void OnApplicationQuit()
    {
        OnDestroy();
    }

    private void OnEnable()
    {
        SetCallbackOutputString( OnOutputString );
    }

    private void OnDisable()
    {
        SetCallbackOutputString( null );
    }

    private IEnumerator Start() 
    {
        yield return StartCoroutine( ExecuteScriptProcess( "test1.nut" ) );
        yield return StartCoroutine( ExecuteScriptInMemoryProcess( "test0.nut" ) );
    }

    private IEnumerator ExecuteScriptProcess( string fileName )
    {
        var filePath = Path.Combine(Application.streamingAssetsPath, fileName );
        var scriptData = "";

        if ( filePath.Contains( "://" ) == true )
        {
            WWW www = new WWW( filePath );
            yield return www;
            scriptData = www.text;
        }
        else
            scriptData = System.IO.File.ReadAllText( filePath );

        //persistentDataPathへコピー
        var outputPath = Path.Combine( Application.persistentDataPath, fileName );
        File.WriteAllText( outputPath, scriptData );
        while( File.Exists( outputPath ) == false )
            yield return null;

        Debug.Log( scriptData );
        ExecuteScript( outputPath );
        yield break;
    }

    private IEnumerator ExecuteScriptInMemoryProcess( string fileName )
    {
        var filePath = Path.Combine( Application.streamingAssetsPath, fileName );
        var scriptData = "";

        if ( filePath.Contains( "://" ) == true )
        {
            WWW www = new WWW( filePath );
            yield return www;
            scriptData = www.text;
        }
        else
            scriptData = File.ReadAllText( filePath );

        Debug.Log( scriptData );
        ExecuteScriptInMemory( scriptData );
        yield break;
    }

    private void OnOutputString( string message )
    {
        Debug.Log( message );
        _instance.SetText( message );
    }

    private void SetText( string message )
    {
        if( _text == null )
            return;
        _text.text = message;
    }
}

Assets/StreamingAssets/test0.nut

print("ハローワールド");

Assets/StreamingAssets/test1.nut

function TestFunc( n, f, s )
{
    print( "TestFunc:" + n + "/" + f + "/" + s );
    return 789;
}

function Main()
{
    print( "ハロー!ワールド" );
    print_args( 987, 654.32, "zxcv" );
}
Main();
3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5