본문 바로가기
공허의 유산/사상의 도구

[Unity3D/WebGL] 브라우저 스크립팅과 상호 작용(1)

by 바른생활머시마 2023. 10. 6.
728x90
반응형

사람들에게 도움이 되는 프로그램의 가장 중요한 미덕(?)은 '접근성'인 것 같고, 그 관점에서 보면 웹 기반으로 만드는 것이 정답인 것 같습니다. 몇 번 시도(?) 했다가 결과까지 가지는 못했던 것인데 또 한번 사부작 시도 해 보겠습니다.

 

이 내용은 Unity3D 문서에도 있는데, 한번 따라가 보도록 하죠. 인터넷 검색을 하니 2021버젼으로 검색이 되었는데 최신 버젼의 문서도 있네요.

https://docs.unity3d.com/kr/2023.2/Manual/webgl-interactingwithbrowserscripting.html

 

브라우저 스크립팅과 상호작용 - Unity 매뉴얼

When building content for the web, you might need to communicate with other elements on your web page. Or you might want to implement functionality using Web APIs which Unity doesn’t expose by default. In both cases, you need to directly interface with t

docs.unity3d.com

 

겸사겸사, 이럴 때마다 Editor도 한번씩 업그레이드 해주면 좋은 것 같네요. WebGL support는 꼭 포함시켜야겠네요.

 

또 이런 공부 할 때는, 동기부여가 되는 샘플을 가지고 하면 더 잘되죠.

설치가 되는 동안, Asset Store에서 이것저것 좀 살펴볼까요.

 

Tanks로 검색을 했더니, 이것이 딱 보이네요. 재밌어 보이는데, Tutorial이군요. 나중에 Tutorial도 한번 살펴보도록 하죠. Add to My Assets~!!

https://assetstore.unity.com/packages/essentials/tutorial-projects/tanks-tutorial-46209

 

Tanks! Tutorial | 자습서 | Unity Asset Store

Get the Tanks! Tutorial package from Unity Technologies and speed up your game development process. Find this & other 자습서 options on the Unity Asset Store.

assetstore.unity.com

 

 

자, 설치는 다 되었고, 프로젝트를 생성합니다.

프로젝트 템플릿은....???

랜더링 모드가 혹시 문제가 되지 않을까 싶어 한번 살펴보니, URP로 해야되겠네요.

 프로젝트 템플릿도 받고~

 

프로젝트를 만들고, Tanks Tutorial부터 받아보겠습니다.

Windows/ Package Manager로 가서 내 에셋에서 찾아 받습니다.

아... 미리 프로젝트를 만들어 놓을 필요가 없었군요.import!

이런 것도...

자, 이어서 가져오겠습니다.

import 후, Complete_Game이라는 Scene을 열고 실행하면~

요렇게 귀엽게 나옵니다. ㅋㅋ. 배경 음악도 아주 신납니다.

이제 저 탱크를 써서 테스트를 해보죠.

 

원래있던 빈 Scene에 import 된 에셋 중, 배경과 탱크를 Scene에 추가합니다.

처음에 배경을 추가하면 너무 눈부시게 되어 있는데, Directional light의 intensity를 0으로 설정하면 됩니다.

실행을 하면 키보드WASD로 탱크를 움직일 수 있습니다.

여기까지는 Javascript 사용하는 것과 아무런 관련이 없어요.ㅋ

그냥 허공에 Cube 하나 띄워놔도 됩니다.

 

Unity 스트립트에서 JavaScript 함수 호출

이제 Unity 기술문서를 좀 볼까요??

무엇인고 하니~~~

Javascript로 된 함수를 Unity 스크립트에서 호출한다는 말인데, 이게 브라우져에 있는, 즉, Unity 외부에 있는 Javascript를 실행한다는 뜻은 아닙니다.

사실, 하고 싶은 것이 브라우져에 있는 함수와 유니티 내부의 함수가 서로 호출 할 수 있게 되길 바라는 것이라, 처음에는 저 문장이 제대로 보여지지 않았습니다. 그래도 이런 것을 배우면서 점진적으로 연동하는 과정으로 가는 것이니 이것도 중요하죠. 어쨌든, C#과 Javascript가 연동된다는 점 자체로, 꽤 재미(?)있기도 합니다.

 

jslib

Unity 프로젝트 내부에 있는 Javascript는 jslib라는 확장자를 가져야 하고, 형식은 아래와 같이 좀 특이한 형식을 취합니다. 다른 샘플 프로젝트에 있는 jslib를 보고 공부 해 보라는데...뭐야....제대로 가르쳐줘야지..

 가이드 문서에 있는 샘플 코드는 아주 다양한 인자의 함수들이 포함되어 있어서, 분명 좋은 샘플인 것 같습니다.

요건 그냥 가이드 문서 내용대로 한번 해보겠습니다.

 

Assets 폴더 아래에 Plugins라는 폴더를 만듭니다.

그리고, 그 속에 *.jslib 파일을 하나 만듭니다.(저는 TankController.jslib라고 만들었습니다.). 이 파일은 Unity Editor에서 만들 수는 없는 것 같아 밖에서 만들었습니다.

이 파일을 편집기에서 열어, 아래 코드를 입력하고 저장합니다. 아래 코드 내용을 살펴보면 간단한 내용이며, 다양한 형태로 인자를 전달하기 위해서 여러가지 함수를 만든 것을 알 수 있습니다. 여기서 눈 여겨 볼 것은, 이 함수들이 Javascript로 만들어진 함수라는 것입니다.  브라우져의 HTML코드에 있는 Javascript가 아니란거죠.!!!  Android library를 Unity에서 호출하듯, Javascript로 된 함수를 Unity가 호출 할 수 있게 해준다는 의미가 있겠습니다. 

 여기서 한 가지 흥미로운 점은, Hello 함수에서 window.alert를 호출한다는 것입니다. 이 window 객체는 브라우져의 객체이죠. 이걸 보면 뭔가 연결의 실마리가 전혀 없지는 않겠다는 희망(?)을 가질 수 있습니다.

mergeInto(LibraryManager.library, {

  Hello: function () {
    window.alert("Hello, world!");
  },

  HelloString: function (str) {
    window.alert(UTF8ToString(str));
  },

  PrintFloatArray: function (array, size) {
    for(var i = 0; i < size; i++)
    console.log(HEAPF32[(array >> 2) + i]);
  },

  AddNumbers: function (x, y) {
    return x + y;
  },

  StringReturnValueFunction: function () {
    var returnStr = "bla";
    var bufferSize = lengthBytesUTF8(returnStr) + 1;
    var buffer = _malloc(bufferSize);
    stringToUTF8(returnStr, buffer, bufferSize);
    return buffer;
  },

  BindWebGLTexture: function (texture) {
    GLctx.bindTexture(GLctx.TEXTURE_2D, GL.textures[texture]);
  },

});

 

저장 후 에디터에서 해당 파일을 선택하면, inspector에 뭐가 설정 되는 것을 볼 수 있습니다. 일단, 우리는 WebGL에서 볼 것이니 그대로 저장하고 둡니다.

 

자, 이제 저 Javascript 함수를 호출할 코드를 탱크에 추가 해 보겠습니다.

Tank에는 아래와 같이 세 개의 스크립트 파일이 있습니다. 이 중, 탱크가 발사를 하면, Hello 함수를 호출하도록 하겠습니다.

 

Javascript코드, 즉, jslib 파일에 정의 된 함수를 호출하려면 

using System.Runtime.InteropServices;

[DllImport("__Internal")]

요 두 가지를 반영해야 합니다.

 

발사하는 스크립트에 한번 반영 해 보겠습니다.

발사 스크립트 파일 상단에 요렇게 반영합니다. Hello 보다는 '쾅!' 팝업을 띄워보면 좋을 것 같아서, 팝업 띄울 문자열을 전달받는 HelloString 함수도 추가 해보았습니다.

using UnityEngine;
using UnityEngine.UI;

using System.Runtime.InteropServices;

namespace Complete
{
    public class TankShooting : MonoBehaviour
    {

        [DllImport("__Internal")]
        private static extern void Hello();

        [DllImport("__Internal")]
        private static extern void HelloString(string str);

        public int m_PlayerNumber = 1;              // Used to identify the different players.
        public Rigidbody m_Shell;                   // Prefab of the shell.
        public Transform m_FireTransform;           // A child of the tank where the shells are spawned.
        public Slider m_AimSlider;                  // A child of the tank that displays the current launch force.
        public AudioSource m_ShootingAudio;         // Reference to the audio source used to play the shooting audio. NB: different to the movement audio source.
        public AudioClip m_ChargingClip;            // Audio that plays when each shot is charging up.
        public AudioClip m_FireClip;                // Audio that plays when each shot is fired.
        public float m_MinLaunchForce = 15f;        // The force given to the shell if the fire button is not held.
        public float m_MaxLaunchForce = 30f;        // The force given to the shell if the fire button is held for the max charge time.
        public float m_MaxChargeTime = 0.75f;       // How long the shell can charge for before it is fired at max force.

 

그 후, 원래 있던 Fire 함수의 가장 끝에, HelloString 함수 호출을 추가했습니다.

        private void Fire ()
        {
            // Set the fired flag so only Fire is only called once.
            m_Fired = true;

            // Create an instance of the shell and store a reference to it's rigidbody.
            Rigidbody shellInstance =
                Instantiate (m_Shell, m_FireTransform.position, m_FireTransform.rotation) as Rigidbody;

            // Set the shell's velocity to the launch force in the fire position's forward direction.
            shellInstance.velocity = m_CurrentLaunchForce * m_FireTransform.forward; 

            // Change the clip to the firing clip and play it.
            m_ShootingAudio.clip = m_FireClip;
            m_ShootingAudio.Play ();

            // Reset the launch force.  This is a precaution in case of missing button events.
            m_CurrentLaunchForce = m_MinLaunchForce;

            HelloString("Pang");
        }

 

이제, 실행 해 보겠습니다.

File/ Build Setting으로 가서 WebGL 빌드로 설정을 합니다. 이때 Scene이 혹시 Complete Scene으로 되어 있다면 변경도 해줘야 하구요. 얼마전까지는 WebGL로 변경하면 텍스쳐 관련 설정 오류가 꼭 났었는데 수정이 되었나보네요. 따로 웹서버 돌리고 있는 것이 있다면, Build해서 그 서버에서 확인하면 되고, 따로 서버가 없으면 Build And Run을 하면 Unity에 내장 된 Nodejs 서버로 실행 시켜 줍니다.

 

결과는 요렇게 뜨고, 스페이스 키 눌러서 실행하면, 기대했던 alert 창도 잘 뜹니다.

어찌되었든, Unity 내부의 스크립트로 브라우져의 무엇인가를 제어한 것이라 고무적이죠. window 객체 뿐만 아니라 document 객체도 접근 할 수 있다면 할 수 있는 것이 훨씬 많아지겠네요.

 

 일단 첫번째 예는 Unity 에서 내부의 Javascript 코드를 실행하는 예였고, 이 예가 실행되는 환경이 브라우져인 경우, window 객체에 접근 가능하다는 것을 확인하였습니다.  다른 객체도 접근 가능하지 않을까 싶으니 차차 확인 해 보기로 하고, 가이드 분서를 좀 더 살펴보도록 하겠습니다.

https://github.com/red112/unity_javascript

 

GitHub - red112/unity_javascript

Contribute to red112/unity_javascript development by creating an account on GitHub.

github.com

 

728x90
반응형

댓글