가상 현실에서 끝없는 주자 게임을 만드는 방법(2부)

게시 됨: 2022-03-10
빠른 요약 ↬ 키보드 없이 VR 헤드셋을 지원하는 게임이 어떻게 만들어졌는지 궁금하다면 이 튜토리얼에서 원하는 것을 설명합니다. 기본적인 기능을 갖춘 VR 게임에 생명을 불어넣을 수 있는 방법은 다음과 같습니다.

이 시리즈의 1부에서는 조명 및 애니메이션 효과가 있는 가상 현실 모델을 만드는 방법을 살펴보았습니다. 이 부분에서 우리는 게임의 핵심 로직을 구현하고 이 응용 프로그램의 "게임" 부분을 구축하기 위해 보다 고급 A-Frame 환경 조작을 사용할 것입니다. 결국, 당신은 실제 도전과제와 함께 작동하는 가상 현실 게임을 갖게 될 것입니다.

이 자습서에는 충돌 감지 및 mixin과 같은 더 많은 A-Frame 개념을 포함하지만 이에 국한되지 않는 여러 단계가 포함됩니다.

  • 최종 제품의 데모

전제 조건

이전 튜토리얼과 마찬가지로 다음이 필요합니다.

  • 인터넷 액세스(특히 glitch.com)
  • 파트 1에서 완료된 Glitch 프로젝트. (https://glitch.com/edit/#!/ergo-1로 이동하여 "Remix to edit"를 클릭하여 완성된 제품에서 계속할 수 있습니다.
  • 가상 현실 헤드셋(옵션, 권장). (저는 개당 15달러에 제공되는 Google Cardboard를 사용합니다.)
점프 후 더! 아래에서 계속 읽기 ↓

1단계: 장애물 설계

이 단계에서는 장애물로 사용할 나무를 디자인합니다. 그런 다음 다음과 같이 나무를 플레이어 쪽으로 이동시키는 간단한 애니메이션을 추가합니다.

플레이어를 향해 움직이는 템플릿 나무
플레이어를 향해 움직이는 템플릿 트리(큰 미리보기)

이 나무는 게임 중에 생성하는 장애물의 템플릿 역할을 합니다. 이 단계의 마지막 부분에서는 이러한 "템플릿 트리"를 제거합니다.

시작하려면 다양한 A-Frame 믹스 인을 추가하세요. 믹스인은 일반적으로 사용되는 구성 요소 속성 집합입니다. 우리의 경우 모든 나무는 동일한 색상, 높이, 너비, 깊이 등을 갖습니다. 즉, 모든 나무가 동일하게 보일 것이며 따라서 몇 가지 공유 믹스인을 사용하게 됩니다.

참고 : 튜토리얼에서 유일한 자산은 믹스인입니다. 자세한 내용은 A-Frame Mixins 페이지를 방문하십시오.

편집기에서 index.html 로 이동합니다. 하늘 바로 뒤와 조명 앞에 새 A-Frame 개체를 추가하여 자산을 보관합니다.

 <a-sky...></a-sky> <!-- Mixins --> <a-assets> </a-assets> <!-- Lights --> ...

a-assets 엔터티에서 폴리지용 믹스인을 추가하는 것으로 시작합니다. 이 믹스인은 템플릿 트리의 잎에 대한 공통 속성을 정의합니다. 요컨대, 낮은 폴리 효과를 위한 흰색의 평평한 음영 피라미드입니다.

 <a-assets> <a-mixin geometry=" primitive: cone; segments-height: 1; segments-radial:4; radius-bottom:0.3;" material="color:white;flat-shading: true;"></a-mixin> </a-assets>

단풍 믹스인 바로 아래에 트렁크용 믹스인을 추가합니다. 이 트렁크는 작은 흰색 직사각형 프리즘이 될 것입니다.

 <a-assets> ... <a-mixin geometry=" primitive: box; height:0.5; width:0.1; depth:0.1;" material="color:white;"></a-mixin> </a-assets>

다음으로 이러한 믹스인을 사용할 템플릿 트리 개체를 추가합니다. 여전히 index.html 에서 플랫폼 섹션까지 아래로 스크롤합니다. 플레이어 섹션 바로 앞에 3개의 빈 트리 항목이 있는 새 트리 섹션을 추가합니다.

 <a-entity ...> <!-- Trees --> <a-entity></a-entity> <a-entity></a-entity> <a-entity></a-entity> <!-- Player --> ...

다음으로 위치를 조정하고 크기를 조정하고 나무 개체에 그림자를 추가합니다.

 <!-- Trees --> <a-entity shadow scale="0.3 0.3 0.3" position="0 0.6 0"></a-entity> <a-entity shadow scale="0.3 0.3 0.3" position="0 0.6 0"></a-entity> <a-entity shadow scale="0.3 0.3 0.3" position="0 0.6 0"></a-entity>

이제 이전에 정의한 믹스인을 사용하여 나무 개체를 줄기와 잎으로 채웁니다.

 <!-- Trees --> <a-entity ...> <a-entity mixin="foliage"></a-entity> <a-entity mixin="trunk" position="0 -0.5 0"></a-entity> </a-entity> <a-entity ...> <a-entity mixin="foliage"></a-entity> <a-entity mixin="trunk" position="0 -0.5 0"></a-entity> </a-entity> <a-entity ...> <a-entity mixin="foliage"></a-entity> <a-entity mixin="trunk" position="0 -0.5 0"></a-entity> </a-entity>

미리 보기로 이동하면 이제 다음 템플릿 트리가 표시됩니다.

장애물에 대한 템플릿 트리
장애물에 대한 템플릿 트리(큰 미리보기)

이제 플랫폼의 먼 위치에서 사용자를 향해 나무를 애니메이션합니다. 이전과 마찬가지로 a-animation 태그를 사용합니다.

 <!-- Trees --> <a-entity ...> ... <a-animation attribute="position" ease="linear" from="0 0.6 -7" to="0 0.6 1.5" dur="5000"></a-animation> </a-entity> <a-entity ...> ... <a-animation attribute="position" ease="linear" from="-0.5 0.55 -7" to="-0.5 0.55 1.5" dur="5000"></a-animation> </a-entity> <a-entity ...> ... <a-animation attribute="position" ease="linear" from="0.5 0.55 -7" to="0.5 0.55 1.5" dur="5000"></a-animation> </a-entity>

코드가 다음과 일치하는지 확인하십시오.

 <a-entity...> <!-- Trees --> <a-entity shadow scale="0.3 0.3 0.3" position="0 0.6 0"> <a-entity mixin="foliage"></a-entity> <a-entity mixin="trunk" position="0 -0.5 0"></a-entity> <a-animation attribute="position" ease="linear" from="0 0.6 -7" to="0 0.6 1.5" dur="5000"></a-animation> </a-entity> <a-entity shadow scale="0.3 0.3 0.3" position="-0.5 0.55 0"> <a-entity mixin="foliage"></a-entity> <a-entity mixin="trunk" position="0 -0.5 0"></a-entity> <a-animation attribute="position" ease="linear" from="-0.5 0.55 -7" to="-0.5 0.55 1.5" dur="5000"></a-animation> </a-entity> <a-entity shadow scale="0.3 0.3 0.3" position="0.5 0.55 0"> <a-entity mixin="foliage"></a-entity> <a-entity mixin="trunk" position="0 -0.5 0"></a-entity> <a-animation attribute="position" ease="linear" from="0.5 0.55 -7" to="0.5 0.55 1.5" dur="5000"></a-animation> </a-entity> <!-- Player --> ...

미리보기로 이동하면 이제 나무가 자신을 향해 움직이는 것을 볼 수 있습니다.

플레이어를 향해 움직이는 템플릿 나무
플레이어 쪽으로 이동하는 템플릿 트리 플레이어 쪽으로 이동하는 템플릿 트리(큰 미리보기)

편집기로 다시 이동합니다. 이번에는 asset/ergo.js 를 선택합니다. 게임 섹션에서 창이 로드된 후 트리를 설정합니다.

 /******** * GAME * ********/ ... window.onload = function() { setupTrees(); }

컨트롤 아래에 있지만 게임 섹션 앞에 새 TREES 섹션을 추가합니다. 이 섹션에서는 새로운 setupTrees 함수를 정의합니다.

 /************ * CONTROLS * ************/ ... /********* * TREES * *********/ function setupTrees() { } /******** * GAME * ********/ ...

새로운 setupTrees 함수에서 템플릿 트리 DOM 개체에 대한 참조를 얻고 참조를 전역적으로 사용할 수 있도록 합니다.

 /********* * TREES * *********/ var templateTreeLeft; var templateTreeCenter; var templateTreeRight; function setupTrees() { templateTreeLeft = document.getElementById('template-tree-left'); templateTreeCenter = document.getElementById('template-tree-center'); templateTreeRight = document.getElementById('template-tree-right'); }

다음으로, 새로운 removeTree 유틸리티를 정의하십시오. 이 유틸리티를 사용하면 장면에서 템플릿 트리를 제거할 수 있습니다. setupTrees 함수 아래에서 새 유틸리티를 정의합니다.

 function setupTrees() { ... } function removeTree(tree) { tree.parentNode.removeChild(tree); }

setupTrees 로 돌아가서 새 유틸리티를 사용하여 템플릿 트리를 제거합니다.

 function setupTrees() { ... removeTree(templateTreeLeft); removeTree(templateTreeRight); removeTree(templateTreeCenter); }

트리 및 게임 섹션이 다음과 일치하는지 확인하십시오.

 /********* * TREES * *********/ var templateTreeLeft; var templateTreeCenter; var templateTreeRight; function setupTrees() { templateTreeLeft = document.getElementById('template-tree-left'); templateTreeCenter = document.getElementById('template-tree-center'); templateTreeRight = document.getElementById('template-tree-right'); removeTree(templateTreeLeft); removeTree(templateTreeRight); removeTree(templateTreeCenter); } function removeTree(tree) { tree.parentNode.removeChild(tree); } /******** * GAME * ********/ setupControls(); // TODO: AFRAME.registerComponent has to occur before window.onload? window.onload = function() { setupTrees(); }

미리보기를 다시 열면 이제 나무가 없어야 합니다. 미리보기는 이 튜토리얼을 시작할 때의 게임과 일치해야 합니다.

1부 완성품
1부 완성품(큰 미리보기)

이것으로 템플릿 트리 디자인을 마칩니다.

이 단계에서는 공통 속성을 정의하여 코드를 단순화할 수 있는 A-Frame 믹스인을 다루고 사용했습니다. 또한 A-Frame VR 장면에서 개체를 제거하기 위해 DOM과 A-Frame 통합을 활용했습니다.

다음 단계에서는 여러 장애물을 생성하고 다른 레인에 나무를 분배하는 간단한 알고리즘을 설계합니다.

2단계: 장애물 생성

끝없는 러너 게임에서 우리의 목표는 우리를 향해 날아오는 장애물을 피하는 것입니다. 이 게임의 특정 구현에서 우리는 가장 일반적으로 3개의 레인을 사용합니다.

대부분의 끝없는 러너 게임과 달리 이 게임은 좌우 이동만 지원 합니다. 이것은 장애물 생성을 위한 알고리즘에 제약을 가합니다. 세 개의 레인 모두에 세 개의 장애물이 동시에 우리를 향해 날아갈 수는 없습니다. 그런 일이 발생하면 플레이어는 생존할 가능성이 0이 됩니다. 결과적으로 생성 알고리즘은 이 제약 조건을 수용해야 합니다.

이 단계에서 모든 코드 편집은 assets/ergo.js 에서 이루어집니다. HTML 파일은 그대로 유지됩니다. assets/ergo.js TREES 트리 섹션으로 이동합니다.

시작하려면 나무를 생성하는 유틸리티를 추가합니다. 모든 나무는 고유한 ID가 필요하며, 이는 나무가 생성될 때 존재하는 나무의 수로 순진하게 정의할 것입니다. 전역 변수의 나무 수를 추적하는 것으로 시작합니다.

 /********* * TREES * *********/ ... var numberOfTrees = 0; function setupTrees() { ...

다음으로, 우리는 스폰 함수가 나무를 추가할 나무 컨테이너 DOM 요소에 대한 참조를 초기화할 것입니다. 여전히 TREES 섹션에서 전역 변수를 추가한 다음 참조를 만듭니다.

 ... var treeContainer; var numberOfTrees ... function setupTrees() { ... templateTreeRight = ... treeContainer = document.getElementById('tree-container'); removeTree(...); ... }

나무의 수와 나무 컨테이너를 모두 사용하여 나무를 생성하는 새 함수를 작성하십시오.

 function removeTree(tree) { ... } function addTree(el) { numberOfTrees += 1; el.id = 'tree-' + numberOfTrees; treeContainer.appendChild(el); } ...

나중에 사용하기 쉽도록 올바른 나무를 올바른 레인에 추가하는 두 번째 함수를 만들 것입니다. 시작하려면 TREES 섹션에서 새 templates 배열을 정의하십시오.

 var templates; var treeContainer; ... function setupTrees() { ... templates = [templateTreeLeft, templateTreeCenter, templateTreeRight]; removeTree(...); ... }

이 템플릿 배열을 사용하여 왼쪽, 중간 또는 오른쪽을 나타내는 ID가 지정된 특정 레인에 나무를 생성하는 유틸리티를 추가합니다.

 function function addTree(el) { ... } function addTreeTo(position_index) { var template = templates[position_index]; addTree(template.cloneNode(true)); }

미리보기로 이동하여 개발자 콘솔을 엽니다. 개발자 콘솔에서 전역 addTreeTo 함수를 호출합니다.

 > addTreeTo(0); # spawns tree in left lane 
수동으로 addTreeTo 호출
수동으로 addTreeTo 호출(큰 미리보기)

이제 무작위로 나무를 생성하는 알고리즘을 작성합니다.

  1. 무작위로 차선을 선택합니다(이 시간 단계에서는 아직 선택되지 않음).
  2. 약간의 확률로 나무를 생성합니다.
  3. 이 시간 단계에 대해 최대 수의 나무가 생성된 경우 중지합니다. 그렇지 않으면 1단계를 반복하십시오.

이 알고리즘을 적용하기 위해 대신 템플릿 목록을 섞고 한 번에 하나씩 처리합니다. 여러 다른 키워드 인수를 허용하는 새 함수 addTreesRandomly 를 정의하는 것으로 시작합니다.

 function addTreeTo(position_index) { ... } /** * Add any number of trees across different lanes, randomly. **/ function addTreesRandomly( { probTreeLeft = 0.5, probTreeCenter = 0.5, probTreeRight = 0.5, maxNumberTrees = 2 } = {}) { }

addTreesRandomly 함수에서 템플릿 트리 목록을 정의하고 목록을 섞습니다.

 function addTreesRandomly( ... ) { var trees = [ {probability: probTreeLeft, position_index: 0}, {probability: probTreeCenter, position_index: 1}, {probability: probTreeRight, position_index: 2}, ] shuffle(trees); }

파일 맨 아래로 스크롤하여 새 shuffle 유틸리티와 함께 ​​새 유틸리티 섹션을 만듭니다. 이 유틸리티는 배열을 제자리에서 섞습니다.

 /******** * GAME * ********/ ... /************* * UTILITIES * *************/ /** * Shuffles array in place. * @param {Array} a items An array containing the items. */ function shuffle(a) { var j, x, i; for (i = a.length - 1; i > 0; i--) { j = Math.floor(Math.random() * (i + 1)); x = a[i]; a[i] = a[j]; a[j] = x; } return a; }

Trees 섹션에서 addTreesRandomly 함수로 다시 이동합니다. 새 변수 numberOfTreesAdded 를 추가하고 위에 정의된 트리 목록을 반복합니다.

 function addTreesRandomly( ... ) { ... var numberOfTreesAdded = 0; trees.forEach(function (tree) { }); }

나무에 대한 반복에서 약간의 확률로 추가된 나무의 수가 2 를 초과하지 않는 경우에만 나무를 생성합니다. 다음과 같이 for 루프를 업데이트합니다.

 function addTreesRandomly( ... ) { ... trees.forEach(function (tree) { if (Math.random() < tree.probability && numberOfTreesAdded < maxNumberTrees) { addTreeTo(tree.position_index); numberOfTreesAdded += 1; } }); }

함수를 종료하려면 추가된 트리 수를 반환합니다.

 function addTreesRandomly( ... ) { ... return numberOfTreesAdded; }

addTreesRandomly 함수가 다음과 일치하는지 다시 확인하십시오.

 /** * Add any number of trees across different lanes, randomly. **/ function addTreesRandomly( { probTreeLeft = 0.5, probTreeCenter = 0.5, probTreeRight = 0.5, maxNumberTrees = 2 } = {}) { var trees = [ {probability: probTreeLeft, position_index: 0}, {probability: probTreeCenter, position_index: 1}, {probability: probTreeRight, position_index: 2}, ] shuffle(trees); var numberOfTreesAdded = 0; trees.forEach(function (tree) { if (Math.random() < tree.probability && numberOfTreesAdded < maxNumberTrees) { addTreeTo(tree.position_index); numberOfTreesAdded += 1; } }); return numberOfTreesAdded; }

마지막으로 나무를 자동으로 생성하려면 정기적으로 나무 생성을 트리거하는 타이머를 설정하십시오. 타이머를 전역적으로 정의하고 이 타이머에 대한 새 분해 기능을 추가합니다.

 /********* * TREES * *********/ ... var treeTimer; function setupTrees() { ... } function teardownTrees() { clearInterval(treeTimer); }

다음으로 타이머를 초기화하고 이전에 정의된 전역 변수에 타이머를 저장하는 새 함수를 정의합니다. 아래 타이머는 0.5초마다 실행됩니다.

 function addTreesRandomlyLoop({intervalLength = 500} = {}) { treeTimer = setInterval(addTreesRandomly, intervalLength); }

마지막으로 창이 로드된 후 게임 섹션에서 타이머를 시작합니다.

 /******** * GAME * ********/ ... window.onload = function() { ... addTreesRandomlyLoop(); }

미리보기로 이동하면 무작위로 생성되는 나무를 볼 수 있습니다. 한 번에 세 그루의 나무가 있는 것은 아닙니다.

무작위로 스폰되는 나무
무작위로 생성되는 나무(큰 미리보기)

이것으로 장애물 단계를 마칩니다. 우리는 성공적으로 많은 템플릿 트리를 가져왔고 템플릿에서 무한한 수의 장애물을 생성했습니다. 또한 우리의 생성 알고리즘은 게임을 플레이할 수 있도록 게임의 자연스러운 제약 조건을 존중합니다.

다음 단계에서는 충돌 테스트를 추가해 보겠습니다.

3단계: 충돌 테스트

이 섹션에서는 장애물과 플레이어 간의 충돌 테스트를 구현합니다. 이러한 충돌 테스트는 대부분의 다른 게임에서 충돌 테스트보다 간단합니다. 그러나 플레이어는 x축을 따라서만 이동하므로 나무가 x축을 지날 때마다 나무의 레인이 플레이어의 레인과 동일한지 확인합니다. 우리는 이 게임에 대해 이 간단한 검사를 구현할 것입니다.

index.html 로 이동하여 TREES 섹션으로 이동합니다. 여기에서는 각 나무에 레인 정보를 추가합니다. 각 트리에 대해 다음과 같이 data-tree-position-index= 를 추가합니다. 추가로 class="tree" 를 추가하여 라인 아래의 모든 트리를 쉽게 선택할 수 있습니다.

 <a-entity data-tree-position-index="1" class="tree" ...> </a-entity> <a-entity data-tree-position-index="0" class="tree" ...> </a-entity> <a-entity data-tree-position-index="2" class="tree" ...> </a-entity>

asset/ergo.js 로 이동하여 GAME 섹션에서 새로운 setupCollisions 함수를 호출합니다. 또한 기존 게임이 이미 실행 중인지 여부를 나타내는 새로운 isGameRunning 전역 변수를 정의합니다.

 /******** * GAME * ********/ var isGameRunning = false; setupControls(); setupCollision(); window.onload = function() { ...

TREES 섹션 바로 뒤에서 Game 섹션 앞에 새로운 COLLISIONS 섹션을 정의하십시오. 이 섹션에서는 setupCollisions 함수를 정의합니다.

 /********* * TREES * *********/ ... /************** * COLLISIONS * **************/ const POSITION_Z_OUT_OF_SIGHT = 1; const POSITION_Z_LINE_START = 0.6; const POSITION_Z_LINE_END = 0.7; function setupCollision() { } /******** * GAME * ********/

이전과 마찬가지로 AFRAME 구성 요소를 등록하고 tick 이벤트 리스너를 사용하여 모든 시간 단계에서 코드를 실행합니다. 이 경우 player 에 구성 요소를 등록하고 해당 수신기의 모든 트리에 대해 검사를 실행합니다.

 function setupCollisions() { AFRAME.registerComponent('player', { tick: function() { document.querySelectorAll('.tree').forEach(function(tree) { } } } }

for 루프에서 트리의 관련 정보를 가져오는 것으로 시작합니다.

 document.querySelectorAll('.tree').forEach(function(tree) { position = tree.getAttribute('position'); tree_position_index = tree.getAttribute('data-tree-position-index'); tree_id = tree.getAttribute('id'); }

다음으로, 여전히 for 루프 내에서 트리의 속성을 추출한 직후에 보이지 않는 경우 트리를 제거합니다.

 document.querySelectorAll('.tree').forEach(function(tree) { ... if (position.z > POSITION_Z_OUT_OF_SIGHT) { removeTree(tree); } }

다음으로, 실행 중인 게임이 없으면 충돌이 있는지 확인하지 마십시오.

 document.querySelectorAll('.tree').forEach(function(tree) { if (!isGameRunning) return; }

마지막으로(여전히 for 루프에 있음) 나무가 플레이어와 동시에 같은 위치를 공유하는지 확인합니다. 그렇다면 아직 정의되지 않은 gameOver 함수를 호출하십시오.

 document.querySelectorAll('.tree').forEach(function(tree) { ... if (POSITION_Z_LINE_START < position.z && position.z < POSITION_Z_LINE_END && tree_position_index == player_position_index) { gameOver(); } }

setupCollisions 함수가 다음과 일치하는지 확인하십시오.

 function setupCollisions() { AFRAME.registerComponent('player', { tick: function() { document.querySelectorAll('.tree').forEach(function(tree) { position = tree.getAttribute('position'); tree_position_index = tree.getAttribute('data-tree-position-index'); tree_id = tree.getAttribute('id'); if (position.z > POSITION_Z_OUT_OF_SIGHT) { removeTree(tree); } if (!isGameRunning) return; if (POSITION_Z_LINE_START < position.z && position.z < POSITION_Z_LINE_END && tree_position_index == player_position_index) { gameOver(); } }) } }) }

이것으로 충돌 설정을 마칩니다. 이제 startGame 및 startGame 시퀀스를 추상화하기 위해 몇 가지 gameOver 기능을 추가합니다. GAME 섹션으로 이동합니다. 다음과 일치하도록 window.onload 블록을 업데이트하고 addTreesRandomlyLoop 를 아직 정의되지 않은 startGame 함수로 바꿉니다.

 window.onload = function() { setupTrees(); startGame(); }

설정 함수 호출 아래에 새 startGame 함수를 만듭니다. 이 함수는 그에 따라 isGameRunning 변수를 초기화하고 중복 호출을 방지합니다.

 window.onload = function() { ... } function startGame() { if (isGameRunning) return; isGameRunning = true; addTreesRandomlyLoop(); }

마지막으로 "Game Over!"를 경고하는 gameOver 를 정의합니다. 지금은 메시지.

 function startGame() { ... } function gameOver() { isGameRunning = false; alert('Game Over!'); teardownTrees(); }

이것으로 끝없는 러너 게임의 충돌 테스트 섹션을 마칩니다.

이 단계에서는 이전에 추가한 A-Frame 구성 요소와 기타 여러 유틸리티를 다시 사용했습니다. 게임 기능을 추가로 재구성하고 적절하게 추상화했습니다. 우리는 이후에 더 완전한 게임 경험을 달성하기 위해 이러한 게임 기능을 보강할 것입니다.

결론

1부에서는 VR 헤드셋 친화적 컨트롤을 추가했습니다. 왼쪽을 보면 왼쪽으로 이동하고 오른쪽을 보면 오른쪽으로 이동합니다. 이 시리즈의 두 번째 부분에서는 기본적이고 작동하는 가상 현실 게임을 구축하는 것이 얼마나 쉬운지 보여 드렸습니다. 끝없는 주자가 당신의 기대와 일치하도록 게임 논리를 추가했습니다. 영원히 달리고 플레이어를 향해 끝없는 일련의 위험한 장애물이 날아가도록 하십시오. 지금까지 가상 현실 헤드셋에 대한 키보드 없는 지원으로 작동하는 게임을 구축했습니다.

다음은 다양한 VR 컨트롤 및 헤드셋에 대한 추가 리소스입니다.

  • VR 헤드셋용 A-Frame
    A-Frame VR이 지원하는 브라우저 및 헤드셋에 대한 설문조사입니다.
  • VR 컨트롤러용 A-Frame
    A-Frame이 상호작용을 위한 다른 대안 외에도 컨트롤러, 3DoF 컨트롤러 및 6DoF 컨트롤러를 지원하지 않는 방법.

다음 부분에서는 몇 가지 마무리 작업을 추가하고 게임 상태 를 동기화하여 멀티플레이어 게임에 한 걸음 더 다가갈 것입니다.