原文傳送門
給博客添加能動的看板娘(Live2D)-將其添加到網頁上吧
葯水制作師|860x740
前言
在上一篇文章里,我介紹了Live2D的一些技術背景和從手游「葯水制作師」中提取模型的方法步驟。在這篇文章里,我將接着講述如何利用WebGL SDK將其添加到網頁上 ——就像本站一樣。
更新:
@保羅寫了一個typecho適用的插件,想要開箱即用的同學可以試試看啦~ 項目介紹
另外還有@DaiDR寫的wordpress版 項目介紹
房產證辦理@小白-白寫的wordpress版,模型是2233 項目介紹
為了效率最大化,不用把有限的時間投入到與bug無限的斗爭和踩坑中;為了不重復造輪子;為了愛與和平 就是因為懶,我們不使用官方的SDK,而是用@EYHN大佬封裝好的庫來加載並顯示模型。相比較官方版本而言,僅需一條命令即可進行模型的加載,無疑方便許多。
但對於「葯水制作師」這款游戲的模型來說,上面所說的庫還需經過一些修改。
修改原因
由於作者的特殊考量,#fea64e4沒有實現對鼠標點擊事件(Event)的處理,而是在鼠標移動時觸發點擊事件,我對此作了修改。
此外模型動作里有眨眼的動作,與默認的眨眼動作存在沖突,我注釋了默認的眨眼動作
有趣的地方:默認的眨眼動作眼睛開合度給的上下限值為-1~1,但「葯水制作師」的眼睛開合度為0.9以上時眼睛會消失
少女盲目分析中
Pio眼睛的消失|1027x704
「葯水制作師」帶有一個久置動作,為了利用好這個動作,我通過監聽mouseout
增加了對其的支持。不用mouseleave
的原因是Firefox對於document
的mouseleave
事件響應實現與Chrome不一致
同樣,「葯水制作師」帶有數個點擊動作,而模型中並不存在HIT_AREA,所以直接點擊無效。我實現了通過model.json
中提供的參數(Parameter)手動定義HIT_AREA。
基於commit #fea64e4修改,修改內容:
- 修復移動鼠標會觸發點擊事件的問題
- 增加鼠標點擊事件
- 移除自帶的眨眼動作
- 增加久置動作與事件支持
- 增加自定義HIT_AREA的方法
- 增加保存當前幀到文件的功能
由於原項目使用了GPL v2開源協議,修改后的代碼已開源至GitHub,若想修改請參考項目hexo-helper-live2d ,對Node.js和JavaScript了解不深,功能實現的比較笨,希望有dalao pr🌝
鑒於該項目仍在活躍開發中,我修改的版本可能會隨時間變化而過時
構建(Build)好的版本可以在這里下載
提示功能(可選)
這部分直接抄自之前的浮動小人,做了部分修改,非必須項,須jQuery支持
若不啟用此部分內容,下一步應作相應修改
此處代碼不可直接套用,應根據自身情況進行修改
腳本(Script)
將以下內容保存為waifu-tips.js
,放至相應目錄
function render(template, context) { var tokenReg = /(\\)?\{([^\{\}\\]+)(\\)?\}/g; return template.replace(tokenReg, function (word, slash1, token, slash2) { if (slash1 || slash2) { return word.replace('\\', ''); } var variables = token.replace(/\s/g, '').split('.'); var currentObject = context; var i, length, variable; for (i = 0, length = variables.length; i < length; ++i) { variable = variables[i]; currentObject = currentObject[variable]; if (currentObject === undefined || currentObject === null) return ''; } return currentObject; }); } String.prototype.render = function (context) { return render(this, context); }; var re = /x/; console.log(re); re.toString = function() { showMessage('哈哈,你打開了控制台,是想要看看我的秘密嗎?', 5000); return ''; }; $(document).on('copy', function (){ showMessage('你都復制了些什么呀,轉載要記得加上出處哦', 5000); }); $.ajax({ cache: true, url: "path/to/waifu-tips.json", dataType: "json", success: function (result){ $.each(result.mouseover, function (index, tips){ $(document).on("mouseover", tips.selector, function (){ var text = tips.text; if(Array.isArray(tips.text)) text = tips.text[Math.floor(Math.random() * tips.text.length + 1)-1]; text = text.render({text: $(this).text()}); showMessage(text, 3000); }); }); $.each(result.click, function (index, tips){ $(document).on("click", tips.selector, function (){ var text = tips.text; if(Array.isArray(tips.text)) text = tips.text[Math.floor(Math.random() * tips.text.length + 1)-1]; text = text.render({text: $(this).text()}); showMessage(text, 3000); }); }); } }); (function (){ var text; if(document.referrer !== ''){ var referrer = document.createElement('a'); referrer.href = document.referrer; text = 'Hello! 來自 <span style="color:#0099cc;">' + referrer.hostname + '</span> 的朋友'; var domain = referrer.hostname.split('.')[1]; if (domain == 'baidu') { text = 'Hello! 來自 百度搜索 的朋友<br>你是搜索 <span style="color:#0099cc;">' + referrer.search.split('&wd=')[1].split('&')[0] + '</span> 找到的我嗎?'; }else if (domain == 'so') { text = 'Hello! 來自 360搜索 的朋友<br>你是搜索 <span style="color:#0099cc;">' + referrer.search.split('&q=')[1].split('&')[0] + '</span> 找到的我嗎?'; }else if (domain == 'google') { text = 'Hello! 來自 谷歌搜索 的朋友<br>歡迎閱讀<span style="color:#0099cc;">『' + document.title.split(' - ')[0] + '』</span>'; } }else { if (window.location.href == 'https://imjad.cn/') { //如果是主頁 var now = (new Date()).getHours(); if (now > 23 || now <= 5) { text = '你是夜貓子呀?這么晚還不睡覺,明天起的來嘛'; } else if (now > 5 && now <= 7) { text = '早上好!一日之計在於晨,美好的一天就要開始了'; } else if (now > 7 && now <= 11) { text = '上午好!工作順利嘛,不要久坐,多起來走動走動哦!'; } else if (now > 11 && now <= 14) { text = '中午了,工作了一個上午,現在是午餐時間!'; } else if (now > 14 && now <= 17) { text = '午后很容易犯困呢,今天的運動目標完成了嗎?'; } else if (now > 17 && now <= 19) { text = '傍晚了!窗外夕陽的景色很美麗呢,最美不過夕陽紅~'; } else if (now > 19 && now <= 21) { text = '晚上好,今天過得怎么樣?'; } else if (now > 21 && now <= 23) { text = '已經這么晚了呀,早點休息吧,晚安~'; } else { text = '嗨~ 快來逗我玩吧!'; } }else { text = '歡迎閱讀<span style="color:#0099cc;">『' + document.title.split(' - ')[0] + '』</span>'; } } showMessage(text, 6000); })(); window.setInterval(showHitokoto,30000); function showHitokoto(){ $.getJSON('https://api.imjad.cn/hitokoto/?cat=&charset=utf-8&length=28&encode=json',function(result){ showMessage(result.hitokoto, 5000); }); } function showMessage(text, timeout){ if(Array.isArray(text)) text = text[Math.floor(Math.random() * text.length + 1)-1]; console.log(text); $('.waifu-tips').stop(); $('.waifu-tips').html(text).fadeTo(200, 1); if (timeout === null) timeout = 5000; hideMessage(timeout); } function hideMessage(timeout){ $('.waifu-tips').stop().css('opacity',1); if (timeout === null) timeout = 5000; $('.waifu-tips').delay(timeout).fadeTo(200, 0); }
文本
將以下內容保存為waifu-tips.json
,放至相應目錄
{ "mouseover": [ { "selector": ".container a[href^='http']", "text": ["要看看 <span style=\"color:#0099cc;\">{text}</span> 么?"] }, { "selector": ".fui-home", "text": ["點擊前往首頁,想回到上一頁可以使用瀏覽器的后退功能哦"] }, { "selector": "#tor_show", "text": ["翻頁比較麻煩嗎,點擊可以顯示這篇文章的目錄呢"] }, { "selector": "#comment_go,.fui-chat", "text": ["想要去評論些什么嗎?"] }, { "selector": "#night_mode", "text": ["深夜時要愛護眼睛呀"] }, { "selector": "#qrcode", "text": ["手機掃一下就能繼續看,很方便呢"] }, { "selector": ".comment_reply", "text": ["要吐槽些什么呢"] }, { "selector": "#back-to-top", "text": ["回到開始的地方吧"] }, { "selector": "#author", "text": ["該怎么稱呼你呢"] }, { "selector": "#mail", "text": ["留下你的郵箱,不然就是無頭像人士了"] }, { "selector": "#url", "text": ["你的家在哪里呢,好讓我去參觀參觀"] }, { "selector": "#textarea", "text": ["認真填寫哦,垃圾評論是禁止事項"] }, { "selector": ".OwO-logo", "text": ["要插入一個表情嗎"] }, { "selector": "#csubmit", "text": "要[提交了嗎,首次評論需要審核,請耐心等待~"] }, { "selector": ".ImageBox", "text": ["點擊圖片可以放大呢"] }, { "selector": "input[name=s]", "text": ["找不到想看的內容?搜索看看吧"] }, { "selector": ".previous", "text": ["去上一頁看看吧"] }, { "selector": ".next", "text": ["去下一頁看看吧"] }, { "selector": ".dropdown-toggle", "text": ["這里是菜單"] }, { "selector": "c-player a.play-icon", "text": ["想要聽點音樂嗎"] }, { "selector": "c-player div.time", "text": ["在這里可以調整<span style=\"color:#0099cc;\">播放進度</span>呢"] }, { "selector": "c-player div.volume", "text": ["在這里可以調整<span style=\"color:#0099cc;\">音量</span>呢"] }, { "selector": "c-player div.list-button", "text": ["<span style=\"color:#0099cc;\">播放列表</span>里都有什么呢"] }, { "selector": "c-player div.lyric-button", "text": ["有<span style=\"color:#0099cc;\">歌詞</span>的話就能跟着一起唱呢"] }, { "selector": ".waifu #live2d", "text": ["干嘛呢你,快把手拿開", "鼠…鼠標放錯地方了!"] } ], "click": [ { "selector": ".waifu #live2d", "text": ["是…是不小心碰到了吧", "蘿莉控是什么呀", "你看到我的小熊了嗎", "再摸的話我可要報警了!⌇●﹏●⌇", "110嗎,這里有個變態一直在摸我(ó﹏ò。)"] } ] }
引入JS
修改header.php
,加入以下內容以創建畫布和提示框:
<div class="waifu"> <div class="waifu-tips"></div> <canvas id="live2d" width="280" height="250" class="live2d"></canvas> </div>
在footer.php
中加入以下內容:
<script async src="path/to/waifu-tips.js"></script> <script src="path/to/live2d.js"></script> <script type="text/javascript"> loadlive2d("live2d", "path/to/model.json"); </script>
上一篇文章提到的model.json
修改為以下內容
其中hit_areas_custom
字段的head_x
和body_x
定義了頭部和身體的HIT_AREA的左上角的坐標,head_y
和body_y
定義了右下角的坐標
坐標可通過啟用DEBUG_MOUSE_LOG
獲取
{ "version":"1.0.0", "model":"model.moc", "textures":[ "textures/default-costume.png" ], "layout":{ "center_x":0.0, "center_y":-0.05, "width":2.0 }, "hit_areas_custom":{ "head_x":[-0.35, 0.6], "head_y":[0.19, -0.2], "body_x":[-0.3, -0.25], "body_y":[0.3, -0.9] }, "motions":{ "idle":[ {"file":"motions/WakeUp.mtn"}, {"file":"motions/Breath1.mtn"}, {"file":"motions/Breath2.mtn"}, {"file":"motions/Breath3.mtn"}, {"file":"motions/Breath5.mtn"}, {"file":"motions/Breath7.mtn"}, {"file":"motions/Breath8.mtn"} ], "sleepy":[ {"file":"motions/Sleeping.mtn"} ], "flick_head":[ {"file":"motions/Touch Dere1.mtn"}, {"file":"motions/Touch Dere2.mtn"}, {"file":"motions/Touch Dere3.mtn"}, {"file":"motions/Touch Dere4.mtn"}, {"file":"motions/Touch Dere5.mtn"}, {"file":"motions/Touch Dere6.mtn"} ], "tap_body":[ {"file":"motions/Touch1.mtn"}, {"file":"motions/Touch2.mtn"}, {"file":"motions/Touch3.mtn"}, {"file":"motions/Touch4.mtn"}, {"file":"motions/Touch5.mtn"}, {"file":"motions/Touch6.mtn"} ], "":[ {"file":"motions/Breath1.mtn"}, {"file":"motions/Breath2.mtn"}, {"file":"motions/Breath3.mtn"}, {"file":"motions/Breath4.mtn"}, {"file":"motions/Breath5.mtn"}, {"file":"motions/Breath6.mtn"}, {"file":"motions/Breath7.mtn"}, {"file":"motions/Breath8.mtn"}, {"file":"motions/Fail.mtn"}, {"file":"motions/Sleeping.mtn"}, {"file":"motions/Success.mtn"}, {"file":"motions/Sukebei1.mtn"}, {"file":"motions/Sukebei2.mtn"}, {"file":"motions/Sukebei3.mtn"}, {"file":"motions/Touch Dere1.mtn"}, {"file":"motions/Touch Dere2.mtn"}, {"file":"motions/Touch Dere3.mtn"}, {"file":"motions/Touch Dere4.mtn"}, {"file":"motions/Touch Dere5.mtn"}, {"file":"motions/Touch Dere6.mtn"}, {"file":"motions/Touch1.mtn"}, {"file":"motions/Touch2.mtn"}, {"file":"motions/Touch3.mtn"}, {"file":"motions/Touch4.mtn"}, {"file":"motions/Touch5.mtn"}, {"file":"motions/Touch6.mtn"}, {"file":"motions/WakeUp.mtn"} ] } }
增加樣式(Style)
.waifu { position: fixed; bottom: 0; left: 0; z-index: 1; font-size: 0; transition: all .3s ease-in-out; -webkit-transform: translateY(3px); transform: translateY(3px); } .waifu:hover { -webkit-transform: translateY(0); transform: translateY(0); } @media (max-width: 768px) { .waifu { display: none; } } .waifu-tips { opacity: 0; width: 250px; height: 70px; margin: -20px 20px; padding: 5px 10px; border: 1px solid rgba(224, 186, 140, 0.62); border-radius: 12px; background-color: rgba(236, 217, 188, 0.5); box-shadow: 0 3px 15px 2px rgba(191, 158, 118, 0.2); font-size: 12px; text-overflow: ellipsis; overflow: hidden; position: absolute; animation-delay: 5s; animation-duration: 50s; animation-iteration-count: infinite; animation-name: shake; animation-timing-function: ease-in-out; } .waifu #live2d{ position: relative; } @keyframes shake { 2% { transform: translate(0.5px, -1.5px) rotate(-0.5deg); } 4% { transform: translate(0.5px, 1.5px) rotate(1.5deg); } 6% { transform: translate(1.5px, 1.5px) rotate(1.5deg); } 8% { transform: translate(2.5px, 1.5px) rotate(0.5deg); } 10% { transform: translate(0.5px, 2.5px) rotate(0.5deg); } 12% { transform: translate(1.5px, 1.5px) rotate(0.5deg); } 14% { transform: translate(0.5px, 0.5px) rotate(0.5deg); } 16% { transform: translate(-1.5px, -0.5px) rotate(1.5deg); } 18% { transform: translate(0.5px, 0.5px) rotate(1.5deg); } 20% { transform: translate(2.5px, 2.5px) rotate(1.5deg); } 22% { transform: translate(0.5px, -1.5px) rotate(1.5deg); } 24% { transform: translate(-1.5px, 1.5px) rotate(-0.5deg); } 26% { transform: translate(1.5px, 0.5px) rotate(1.5deg); } 28% { transform: translate(-0.5px, -0.5px) rotate(-0.5deg); } 30% { transform: translate(1.5px, -0.5px) rotate(-0.5deg); } 32% { transform: translate(2.5px, -1.5px) rotate(1.5deg); } 34% { transform: translate(2.5px, 2.5px) rotate(-0.5deg); } 36% { transform: translate(0.5px, -1.5px) rotate(0.5deg); } 38% { transform: translate(2.5px, -0.5px) rotate(-0.5deg); } 40% { transform: translate(-0.5px, 2.5px) rotate(0.5deg); } 42% { transform: translate(-1.5px, 2.5px) rotate(0.5deg); } 44% { transform: translate(-1.5px, 1.5px) rotate(0.5deg); } 46% { transform: translate(1.5px, -0.5px) rotate(-0.5deg); } 48% { transform: translate(2.5px, -0.5px) rotate(0.5deg); } 50% { transform: translate(-1.5px, 1.5px) rotate(0.5deg); } 52% { transform: translate(-0.5px, 1.5px) rotate(0.5deg); } 54% { transform: translate(-1.5px, 1.5px) rotate(0.5deg); } 56% { transform: translate(0.5px, 2.5px) rotate(1.5deg); } 58% { transform: translate(2.5px, 2.5px) rotate(0.5deg); } 60% { transform: translate(2.5px, -1.5px) rotate(1.5deg); } 62% { transform: translate(-1.5px, 0.5px) rotate(1.5deg); } 64% { transform: translate(-1.5px, 1.5px) rotate(1.5deg); } 66% { transform: translate(0.5px, 2.5px) rotate(1.5deg); } 68% { transform: translate(2.5px, -1.5px) rotate(1.5deg); } 70% { transform: translate(2.5px, 2.5px) rotate(0.5deg); } 72% { transform: translate(-0.5px, -1.5px) rotate(1.5deg); } 74% { transform: translate(-1.5px, 2.5px) rotate(1.5deg); } 76% { transform: translate(-1.5px, 2.5px) rotate(1.5deg); } 78% { transform: translate(-1.5px, 2.5px) rotate(0.5deg); } 80% { transform: translate(-1.5px, 0.5px) rotate(-0.5deg); } 82% { transform: translate(-1.5px, 0.5px) rotate(-0.5deg); } 84% { transform: translate(-0.5px, 0.5px) rotate(1.5deg); } 86% { transform: translate(2.5px, 1.5px) rotate(0.5deg); } 88% { transform: translate(-1.5px, 0.5px) rotate(1.5deg); } 90% { transform: translate(-1.5px, -0.5px) rotate(-0.5deg); } 92% { transform: translate(-1.5px, -1.5px) rotate(1.5deg); } 94% { transform: translate(0.5px, 0.5px) rotate(-0.5deg); } 96% { transform: translate(2.5px, -0.5px) rotate(-0.5deg); } 98% { transform: translate(-1.5px, -1.5px) rotate(-0.5deg); } 0%, 100% { transform: translate(0, 0) rotate(0); } }
如一切正常,刷新網頁后,可愛的Pio就會出現在頁面左下角,點擊會播放不同的動作並有相應提示文字