VキャスのVCIを使ったプロトコル「SAC-Craft」のススメ

こんにちは
しきはふりと申します

昨日2022年8月1日から、バーチャルキャストのルーム（配信ではなくVRSNSを目指して作られているVキャスの世界）で開催されている「[夏キャスマーケット2022](https://casmarket.github.io/9/)」の剣のルームに
こんなブース展示を行っています！
![2022080122080175_1. 「剣《ソード》」《夏キャスマーケット2022》_しきはふり.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222644/e1943b72-d1fc-0bd6-a296-bf27e837213e.jpeg)
https://room.virtualcast.jp/rooms/Vx7DEd5ICvfPkrTE

見てわかるように、**敵を倒す**ブースです！
Vキャスに入れれば「素手」「剣」「武器っぽいVCI」で攻撃して敵を倒すことができます。

しかしそれだけでは物足りない！
せっかくの**VCIというアイテムが独立しているバーチャルキャスト**なので・・・
### モンスターを倒す武器を作って遊びませんか？
これがこの記事を書いている理由です！

このブースの敵と武器は、VCIの特定のメッセージ関数とわたしの決めた**SAC-Craft**というプロトコル(構文)を使って制御しています。
つまり、そのルールに則ったメッセージ関数を使って、文字列を送信してくれれば、
**オリジナル武器が作ることができます！**

また、写真に出ている敵は、見た目通り火のモンスターなので、水属性の攻撃がよく効いてほしいですよね？
わたしの用意した[デフォルトの剣](https://seed.online/products/329c4222cfa181e849e532471f7067284825b092feee03bf16aecabc71f2ffb5)は、水属性ではないので、このモンスターに対して、大きなダメージは与えられません。
![5636ecd159eb7dd4edd771ca360d72f594c8693ebbeff9f4c443bf430665465e.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222644/f388f783-27f3-95ac-7fcd-1916eb124975.png)

しかし「**SAC-Craft**」に沿えば水属性を持った武器をオリジナルで作成することが可能です！

以下の手順に従ってLuaコードを設定したVCIを作れば、
対応武器を作ることができるので、ぜひやってみてください！


## 武器の作り方

### ① 武器本体を用意します
自力でBlenderなどで作るのはもちろん、
BOOTHやUnityAssetsストアで購入するのもありだと思います！
※ちなみにわたしのブースの剣は[Low Poly Asset Bundle](https://assetstore.unity.com/packages/3d/environments/low-poly-asset-bundle-196214)というUnityAssesストアで購入したものです。

### ② 武器にコライダーとVCISubItemを設定します
VCIの敵に当てる部分にはコライダーとVCISubItemをつけてください。
IsTriggerとIsKinematicにはチェックを入れ、UseGravityのチェックは外しておきましょう。

### ③ 以下の黒いコード領域を全部コピーしてメモ帳に貼ります
```lua
--[[
    【Unityでの下準備】
    VCIの敵に当てる部分にはコライダートVCISubItemをつけてください。
    IsTriggerとIsKinematicにはチェックを入れ、UseGravityのチェックは外しておきましょう。

    【作業】
    ①　メモ帳などで、<<以下部分が変更箇所>>と<<以上部分が変更箇所>>に挟まれたエリアの数字を好きな値に変更してください。
    ②　メモ帳で書き換えた部分を含む、全ての部分をコピーしてください。
    ③　UnityでVCIObjectのScriptのSouceの部分にコピーしたものを貼り付けてください。
    ④　VCIとして書き出して、TSOにアップロードして使える・・・!

    ※　質問や拡張提案などは、しきはふり(Twitter:@Ntou4)へ
]]--

--------------------<<以下部分が変更箇所>>--------------------

-- 武器の基礎的な情報を書き込みます
--  ※合計値は300までが有効です。それ以上になると正規化されるので、思ったより攻撃などが小さくなるかもしません。
local core = {
    en=200, -- 耐久値を設定します
    at=200, -- 攻撃値を設定します
    de=10,  -- 防御値を設定します　※202208時点では設定のみで効果はありません。
    dr=2,   -- 採掘値を設定します　※202208時点では設定のみで効果はありません。
    mi=2,   -- 掘削値を設定します　※202208時点では設定のみで効果はありません。
    fe=10,  -- 伐採値を設定します　※202208時点では設定のみで効果はありません。
    fi=10,  -- 火属性を設定します。金属性の敵に対して攻撃力が増します。
    wa=10,  -- 水属性を設定します。火属性の敵に対して攻撃力が増します。
    so=10,  -- 土属性を設定します。水属性の敵に対して攻撃力が増します。
    go=10,  -- 金属性を設定します。木属性の敵に対して攻撃力が増します。
    wo=10,  -- 木属性を設定します。土属性の敵に対して攻撃力が増します。
    li=10,  -- 光属性を設定します。闇属性の敵に対して攻撃力が増します。
    da=10   -- 闇属性を設定します。光属性の敵に対して攻撃力が増します。
}

-- 武器の部位効果情報を書き込みます
--  ※合計値は30までが有効です。それ以上になると正規化されるので、思ったより攻撃などが小さくなるかもしません。
local site = {
    at=2, -- 攻撃値を設定します
    de=0,  -- 防御値を設定します　※202208時点では設定のみで効果はありません。
    dr=2,   -- 採掘値を設定します　※202208時点では設定のみで効果はありません。
    mi=2,   -- 掘削値を設定します　※202208時点では設定のみで効果はありません。
    fe=0,  -- 伐採値を設定します　※202208時点では設定のみで効果はありません。
    fi=0,  -- 火属性を設定します。金属性の敵に対して攻撃力が増します。
    wa=0,  -- 水属性を設定します。火属性の敵に対して攻撃力が増します。
    so=0,  -- 土属性を設定します。水属性の敵に対して攻撃力が増します。
    go=0,  -- 金属性を設定します。木属性の敵に対して攻撃力が増します。
    wo=0,  -- 木属性を設定します。土属性の敵に対して攻撃力が増します。
    li=2,  -- 光属性を設定します。闇属性の敵に対して攻撃力が増します。
    da=0   -- 闇属性を設定します。光属性の敵に対して攻撃力が増します。
}
--------------------<<以上部分が変更箇所>>--------------------


-- 設定した攻撃や属性情報を文字列に変換する関数
function toWeapon(param)
    local res =""
    for k, v in pairs(param) do
        res=res..k..":"..v..","
    end
    return string.sub(res,1,-2)
end

-- ランダム文字列を返す関数
function CreateRandStr(length)
    local length = length or 0
    if length <= 0 then
        return nil
    end
    local random_strs = {}
    if length > 0 then
        math.randomseed(os.time())
        for i= 1, length do 
            local a = math.random(97,122) -- a~zのどれかを指定する数字
            local c = math.random(65,90)  -- A~Zのどれかを指定する数字
            local i = math.random(48,57)  -- 0~9のどれかを指定する数字
            local w = math.random(1,3)    -- 大文字、小文字、数字を決めるランダム値
            if w == 1 then table.insert(random_strs, string.char(a)) end
            if w == 2 then table.insert(random_strs, string.char(c)) end
            if w == 3 then table.insert(random_strs, string.char(i)) end
        end
    end
    return table.concat(random_strs, "")
end

-- 同じ武器でも、他のユーザや別に出したものが別物である事がわかるように、ランダム文字列で武器にIDをつけている処理
if vci.assets.IsMine then
    SACCraftItemID = CreateRandStr(10)
    vci.state.Set("SACCraftItemID", CreateRandStr(10))
else
    SACCraftItemID = vci.state.Get("SACCraftItemID")
end

-- 「sac_craft_」が含まれるもの（ブースの敵）に当たったときに、メッセージ関数「SACCraftMessage」で設定した攻撃や属性値を送信する処理
function onTriggerExit(item, hit)
    if string.find(hit, "sac_craft_") then
        vci.message.Emit("SACCraftMessage", SACCraftItemID.."_"..toWeapon(site).."_"..toWeapon(core).."_"..hit)
        --vci.assets._ALL_PlayAudioFromName("nc211865")
    end
end
```

### ④ コード内の<<以下部分が変更箇所>>と<<以上部分が変更箇所>>に挟まれたエリアの数字を好きな値に変更

武器の能力設定を設定します。
- 耐久値:en 武器の使える回数がこの数字で決まります。
- 攻撃値:at 武器の攻撃力がこの数字で決まります。
- 防御値:de ※202208時点では設定のみで効果はありません。
- 採掘値:dr ※202208時点では設定のみで効果はありません。
- 掘削値:mi ※202208時点では設定のみで効果はありません。
- 伐採値:fe ※202208時点では設定のみで効果はありません。
- 火属性:fi 金属性の敵に対して攻撃力が増します。
- 水属性:wa 火属性の敵に対して攻撃力が増します。
- 土属性:so 水属性の敵に対して攻撃力が増します。
- 金属性:go 木属性の敵に対して攻撃力が増します。
- 木属性:wo 土属性の敵に対して攻撃力が増します。
- 光属性:li 闇属性の敵に対して攻撃力が増します。
- 闇属性:da 光属性の敵に対して攻撃力が増します。

色々設定してみてください！

### ⑤ Unityで④のコードを貼って出力＆TSOへアップロード
メモ帳で、武器の攻撃力や属性の編集をしたら、
それをそのままUnityのVCIObjectのSourceに貼り付けてください。

これで、このVCIが敵キャラに当たったときに、攻撃ができるようになります。
VCI出力をしてTSOにアップロードしたら、ブースで使ってみてください！

### 参考データ
参考にわたしの「剣Lv.1」のコードを添付しておきます。
剣Lv.1は当たる場所を3種類用意しているので、今回のコピペコードより少しだけ複雑です。
```lua
local _Parent = vci.assets.GetSubItem("SWORD_CORE")
local sca = 1
local parent = {0,0.379,0,Quaternion.Euler(0,0,0),1,1,1}
local positions = {
    SWORD_TIP={0, 1.343, 0,Quaternion.Euler(0,0,0),0.19461,0.14611,0.05916802},
    SWORD_BELLY={0,0.878,0,Quaternion.Euler(0,0,0),0.19461,0.7875037,0.05916802},
    SWORD_MINE={0,0.878,0,Quaternion.Euler(0,0,0),0.19461,0.7875037,0.05916802}
}
local core = {en=100,at=80,de=10,dr=2,mi=2,fe=10,fi=0,wa=0,so=0,go=0,wo=0,li=7,da=7}
local site1 = {at=2,de=0,dr=2,mi=2,fe=0,fi=0,wa=0,so=0,go=0,wo=0,li=2,da=0}
local site2 = {at=2,de=0,dr=0,mi=0,fe=3,fi=0,wa=0,so=0,go=0,wo=0,li=0,da=2}
local site3 = {at=0,de=10,dr=0,mi=0,fe=0,fi=0,wa=0,so=9,go=0,wo=0,li=0,da=0}

function toweapon(param)
    local res =""
    for k, v in pairs(param) do
        res=res..k..":"..v..","
    end
    return string.sub(res,1,-2)
end


function CreateRandStr(length)
    local length = length or 0
    if length <= 0 then
        return nil
    end
    local random_strs = {}
    if length > 0 then
        math.randomseed(os.time())
        for i= 1, length do 
            local a = math.random(97,122) -- a~zのどれかを指定する数字
            local c = math.random(65,90)  -- A~Zのどれかを指定する数字
            local i = math.random(48,57)  -- 0~9のどれかを指定する数字
            local w = math.random(1,3)    -- 大文字、小文字、数字を決めるランダム値
            if w == 1 then table.insert(random_strs, string.char(a)) end
            if w == 2 then table.insert(random_strs, string.char(c)) end
            if w == 3 then table.insert(random_strs, string.char(i)) end
        end
    end
    return table.concat(random_strs, "")
end

  
if vci.assets.IsMine then
    SACCraftItemID = CreateRandStr(10)
    vci.state.Set("SACCraftItemID", CreateRandStr(10))
else
    SACCraftItemID = vci.state.Get("SACCraftItemID")
end

function updateAll()
    for k, v in pairs(positions) do
        local pos = _Parent.GetPosition()
        sca = _Parent.GetLocalScale().x/parent[5]
        local offset = _Parent.GetRotation() * (Vector3.__new(v[1]*sca,(v[2]-parent[2])*sca,(v[3]-parent[3])*sca))
        pos = pos + offset
        local _Child = vci.assets.GetSubItem(k)
        _Child.SetPosition(pos)
       local rot = _Parent.GetRotation() * v[4]
        _Child.SetRotation(rot)
       local scale = (Vector3.__new(sca*v[5],sca*v[6],sca*v[7]))
        _Child.SetLocalScale(scale)   
    end
end

function onTriggerExit(item, hit)
    if string.find(hit, "sac_craft_") then
        if item == "SWORD_TIP" then
            vci.message.Emit("SACCraftMessage", SACCraftItemID.."_"..toweapon(site1).."_"..toweapon(core).."_"..hit)
            vci.assets._ALL_PlayAudioFromName("nc122001")
        elseif item == "SWORD_BELLY" then
            vci.message.Emit("SACCraftMessage", SACCraftItemID.."_"..toweapon(site2).."_"..toweapon(core).."_"..hit)
            vci.assets._ALL_PlayAudioFromName("nc122001")
        elseif item == "SWORD_MINE" then
            vci.message.Emit("SACCraftMessage", SACCraftItemID.."_"..toweapon(site3).."_"..toweapon(core).."_"..hit)
            vci.assets._ALL_PlayAudioFromName("nc211865")
        end
    end
end
```

## **SAC-Craft**の今現在固まっている仕様
- メッセージ関数「SACCraftMessage」を利用する
- メッセージ関数「SACCraftMessage」で送信する文字列は4種類の情報を決まった順で「_」で区切った文字列とする
    - 1つ目：アイテム識別用の文字列。上記では同じVCIでも別物と判断するためにランダム文字列
    - 2つ目：武器の部位の属性効果値文字列（攻撃値:at、防御値:de、採掘値:dr、掘削値:mi、伐採値:fe、火属性:fi、水属性:wa、土属性:so、金属性:go、木属性:wo、光属性:li、闇属性:daを「,」でつなげた文字列）
    - 3つ目：武器の基本属性効果値文字列（攻撃値:at、攻撃値:at、防御値:de、採掘値:dr、掘削値:mi、伐採値:fe、火属性:fi、水属性:wa、土属性:so、金属性:go、木属性:wo、光属性:li、闇属性:daを「,」でつなげた文字列）
    - 4つ目：当たったVCISubItem名(「sac_craft_」を含むことで、SAC-Craft対象アイテムであると判定する)
    - ※区切り文字に「_」「:」「,」を利用しているため、これら文字は使うことはできない

## おわりに

このブースで提案した「**SAC-Craft**」は、今回のブースに限って考えたものではありません。
- 武器
- 防具
- 敵キャラ
- お仲間キャラ
- 素材
- 食べ物
- VR内の道具
 
など、拡張していけたらいいなと思っています！


多分、この共通ルールに従ってアイテムを作って持ち寄ればいろんなことができます。
例えば・・・
- 今回のブースをより拡張したRPGゲーム
- 複数人でのダンジョン経営
- Vキャスの中に○in○craftみたいなゲームをVCIで作り出す
- このSAC-Craftを使ったゲームジャム？の企画
等々・・・

**SAC-Craft**の名前の由来は、
攻殻機動隊の「Stand Alone Complex：孤立した個人（スタンドアローン）でありながらも全体として集団的な行動（コンプレックス）を取ること」からとっています。

わたしだけでは発想力や実装力に限界があります。
またコードや仕組みも拙いもので、プログラムが強い人から見たら見るに耐えなものかもしれません。
しかしながら、もし、この遊び方に可能性を感じてくださる方がいらっしゃれば、
ぜひ勝手に拡張したり、真似したり、改変してください！

そして、孤立した個人たちの創作の結果でありながらも、**集団的な大きな価値の創出**につながっていくのだとしたら、非常に嬉しく思います。

もし、この試みに興味を持った方がいらっしゃれば、しきはふり（Twitter：＠Ntou4)にご連絡ください。


---------------------------
利用アイテム
・[Low Poly Asset Bundle](https://assetstore.unity.com/packages/3d/environments/low-poly-asset-bundle-196214) JustCreateさま
・[斬撃音　ザシュッ　 【効果音・サウンドエフェクト】](https://commons.nicovideo.jp/material/nc122001)ドリアンジーナさま
・[[SE]　盾(軽量)で防ぐ / 攻撃する音・盾をぶつける音　[高音質]](https://commons.nicovideo.jp/material/nc211865)音屋　【効果音・映像屋】さま
・[Monsters Ultimate Pack 06 Cute Series](https://assetstore.unity.com/packages/3d/characters/creatures/monsters-ultimate-pack-06-cute-series-203135)Meshtint Studioさま