이번에는 Legend of Legend를 모방한 JS 게임을 들고 왔습니다. JS를 모방한 Legend of Legend 게임의 주의 사항은 다음과 같습니다.
머리말
첫 번째 버전의 게임은 2014년에 개발되었습니다. 브라우저는 html+css+js, 서버는 asp+php, 통신은 ajax, 데이터 저장소는 access+mySql을 사용합니다. 하지만 몇 가지 문제로 인해 (당시에는 노드 사용법을 몰랐기 때문에 ASP에서 복잡한 로직을 작성하는 것이 정말 어려웠습니다. 당시에는 캔버스에 쓰기가 거의 없었고 DOM 렌더링이 쉽게 성능 병목 현상에 도달할 수 있었습니다) , 버려졌습니다. 나중에 캔버스를 사용하여 버전이 다시 만들어졌습니다. 이 글은 2018년에 작성되었습니다.
1. 개발 전 준비
비교적 복잡한 PC 게임을 구현하기 위해 Javascript를 사용해야 하는 이유
1.js PC 온라인 게임 구현이 가능합니다. PC와 휴대폰 하드웨어 구성의 업그레이드, 브라우저의 업데이트, 다양한 H5 라이브러리의 개발로 인해 js로 온라인 게임을 구현하는 것이 점점 더 어려워지고 있습니다. 여기서 어려움은 주로 두 가지 측면에 있습니다. 브라우저의 성능, js 코드가 매우 복잡한 논리를 가진 게임의 반복을 충족할 만큼 확장하기 쉬운지 여부입니다.
2. 현재 js 게임 중에는 참고할만한 대규모 게임이 거의 없습니다. 멀티 플레이어 연결, 서버 측 데이터 저장 및 복잡한 상호 작용과 관련된 대부분의(거의 모든) 게임은 Flash를 사용하여 개발됩니다. 하지만 결국 플래시는 쇠퇴하고 있는 반면, js는 빠르게 발전하고 있으며 브라우저가 있는 한 실행할 수 있습니다.
내가 2001년 Legend of Blood 게임을 선택한 이유는
첫 번째 이유는 물론 오래된 게임에 대한 느낌 때문이고, 다른 더 중요한 이유는 내가 다른 게임을 할 줄 모르거나, 연주하는 방법은 알지만 자료(그림, 음향 효과 등)가 없습니다. 게임의 맵, 캐릭터 및 몬스터 모델, 아이템, 장비 다이어그램 등을 수집하고 이를 처리하고 파싱한 후 js 개발에 사용하는 데 많은 노력을 쏟는 것은 시간 낭비라고 생각합니다.
이전에 Legend 게임에 대한 자료를 수집한 적이 있고 운 좋게 Legend of Legend 클라이언트 리소스 파일(github 주소)을 추출하는 방법을 찾았으므로 직접 코드 작성을 시작할 수 있어 준비 시간이 절약됩니다.
가능한 어려움
1. 브라우저 작동 성능: 이것이 가장 어려운 점일 것입니다. 게임이 40프레임을 유지하려는 경우 js가 계산할 수 있도록 각 프레임에는 25ms만 남습니다. 그리고 렌더링은 일반적으로 계산보다 더 많은 성능을 소비하므로 js에 실제로 남은 시간은 약 10밀리초에 불과합니다.
2. 부정 행위 방지: 사용자가 인터페이스를 직접 호출하거나 네트워크 요청 데이터를 변조하는 것을 방지하는 방법은 무엇입니까? 목표는 js를 사용하여 보다 복잡한 게임을 구현하는 것이고 모든 온라인 게임은 이를 고려해야 하기 때문에 상대적으로 성숙한 솔루션이 있어야 합니다. 이것은 이 글의 초점이 아닙니다.
2. 전체적인 디자인
브라우저 측면
화면 렌더링은 캔버스를 사용합니다.
dom(p)+css에 비해 캔버스는 더 복잡한 장면 렌더링 및 이벤트 관리를 처리할 수 있습니다. 예를 들어 다음 장면에는 플레이어, 동물, 지상의 항목 및 가장 낮은 지도 그림의 4개 그림이 포함됩니다. (사실 바닥에 그림자도 있는데, 마우스가 캐릭터, 동물, 사물을 가리킬 때 나타나는 해당 이름과 땅에 보이는 그림자도 있습니다. 읽기의 편의를 위해 내용은 그렇게 많이 고려하지 않겠습니다. )
이때, "동물을 클릭하면 동물을 공격하고 아이템을 클릭하면 아이템을 집는다" 효과를 얻으려면 동물과 아이템에 대한 이벤트를 모니터링해야 합니다. dom 방식을 사용하면 처리하기 어려운 몇 가지 문제가 발생합니다:
a. 렌더링 순서가 이벤트 처리 순서와 다릅니다(때로는 작은 z-index가 이벤트를 먼저 처리해야 함). 추가 처리가 필요합니다. 예를 들어 위의 예에서는 몬스터나 아이템을 클릭할 때 캐릭터를 클릭하기 쉽기 때문에 해당 캐릭터에 대해 "클릭 이벤트 침투" 처리를 수행해야 합니다. 게다가 이벤트 처리 순서는 고정되어 있지 않습니다. 캐릭터를 해제해야 하는 스킬(예: 게임 내 치료)이 있는 경우 이때 캐릭터에 이벤트 모니터링이 필요합니다. 따라서 요소가 이벤트를 처리해야 하는지 여부와 이벤트가 처리되는 순서는 게임 상태에 따라 다르며 DOM의 이벤트 바인딩은 더 이상 요구 사항을 충족할 수 없습니다.
b. 동일한 dom 노드에 넣는 것은 어렵습니다. 플레이어의 모델, 플레이어의 이름, 플레이어의 스킬 효과 등은
컨테이너에서는 관리가 쉽습니다(이런 방식으로 여러 요소의 위치 지정을 상위 요소에서 상속할 수 있으며 위치를 별도로 처리할 필요가 없습니다). 하지만 이렇게 하면 z-index를 처리하기가 어려워집니다. 예를 들어, 플레이어 A가 플레이어 B 위에 있으면 A는 B에 의해 가려집니다. 따라서 A의 z-index는 더 작아야 하지만 플레이어 A의 이름은 B의 이름이나 그림자에 의해 가려져서는 안 됩니다. . 간단히 말해서 DOM 구조의 유지 관리 가능성은 화면 표시 효과를 희생하게 되며 그 반대의 경우도 마찬가지입니다.
c. 성능 문제. 효과가 희생되더라도 렌더링에 DOM을 사용하면 필연적으로 많은 중첩 관계가 발생하고 모든 요소의 스타일이 자주 변경되어 브라우저의 다시 그리기 또는 리플로우가 지속적으로 트리거됩니다.
캔버스 렌더링 로직은 프로젝트 로직과 분리됩니다
캔버스의 다양한 렌더링 작업(예: drawImage, fillText 등)이 프로젝트 코드에 함께 포함되면 나중에 프로젝트를 유지할 수 없게 될 수밖에 없습니다. 여러 기존 캔버스 라이브러리를 살펴본 후 vue의 데이터 바인딩+ 디버깅 도구와 결합하여 새로운 캔버스 라이브러리 Easycanvas(github 주소)를 만들었고 vue와 마찬가지로 플러그인 요소를 통해 캔버스 디버깅을 지원합니다.
이렇게 하면 게임 전체의 렌더링 부분이 훨씬 쉬워집니다. 게임의 현재 상태를 관리하고 서버가 소켓에서 반환한 데이터를 기반으로 데이터를 업데이트하기만 하면 됩니다. Easycanvas는 "데이터 변경으로 인해 뷰가 변경됩니다"라는 링크를 담당합니다. 예를 들어 아래 그림의 플레이어 포장 항목 구현에서는 포장 컨테이너의 위치와 배낭에 있는 각 요소의 배열 규칙만 제공한 다음 포장된 각 항목을 배열에 바인딩하고 그런 다음 예(Easycanvas는 데이터를 화면에 매핑하는 프로세스를 담당합니다).
예를 들어 5행 8열 총 40개 항목의 스타일을 다음과 같은 형태로 Easycanvas에 전달할 수 있습니다(index는 항목 인덱스, x 방향 항목 간 거리는 36, y 방향의 거리는 32입니다.) 그리고 이 논리는 항목 배열이 어떻게 변경되거나 패키지가 드래그되는 위치에 관계없이 변경할 수 없습니다. 각 항목의 상대적 위치는 고정됩니다. 캔버스 렌더링의 경우 프로젝트 자체를 고려할 필요가 없으므로 유지 관리성이 더 좋습니다.
style: { tw: 30, th: 30, tx: function () { return 40 + index % 8 * 36; }, ty: function () { return 31 + Math.floor(index / 8) * 32; } }
캔버스 레이어 렌더링
가정: 게임은 40프레임을 유지해야 하며, 브라우저는 가로 800, 세로 600, 영역 480,000(이하 화면 영역으로 480,000)입니다.
동일한 캔버스를 사용하여 렌더링하는 경우 이 캔버스의 프레임 번호는 40이고 초당 최소 40개의 화면 영역을 그려야 합니다. 그러나 동일한 좌표점에서 여러 요소가 겹칠 가능성이 높습니다. 예를 들어 하단의 UI, 체력 표시줄, 버튼이 겹쳐서 장면 맵을 공동으로 차단합니다. 따라서 이를 합치면 브라우저의 초당 그리기 양은 쉽게 100개 이상의 화면 영역에 도달할 수 있습니다.
이 그림은 뷰가 전체 캔버스의 모든 곳에서 업데이트되기 때문에 최적화하기 어렵습니다. 플레이어와 동물의 움직임일 수도 있고, 버튼의 특수 효과일 수도 있고, 특정 스킬 효과의 변화일 수도 있습니다. . 이 경우 플레이어가 움직이지 않더라도 옷이 "바람에 펄럭이는" 효과(실제로는 스프라이트 애니메이션이 다음 그림으로 재생됨)나 물약 병이 나타나는 효과로 인해 캔버스 전체가 다시 그려집니다. 땅. 게임의 특정 프레임이 이전 프레임과 구별되지 않는 것은 거의 불가능하기 때문에 게임 화면의 일부라도 그대로 유지하기 어렵습니다. 전체 게임 화면은 항상 업데이트됩니다.
게임의 특정 프레임이 이전 프레임과 구별되지 않는 것은 거의 불가능하고 화면은 항상 업데이트되기 때문입니다.
그래서 이번에는 캔버스 세 장을 겹쳐 배치하는 방식을 채택했습니다. Easycanvas의 이벤트 처리는 전달을 지원하므로 상단 캔버스를 클릭하더라도 클릭을 종료하는 요소가 없으면 후속 캔버스도 이벤트를 수신할 수 있습니다. 세 개의 캔버스는 UI, 땅(지도), 엘프(캐릭터, 동물, 스킬 효과 등)를 담당합니다.
이 레이어링의 장점은 레이어당 최대 프레임 수를 필요에 따라 조정하세요:
예를 들어 UI 레이어의 경우, 대부분의 UI는 일반적으로 움직이지 않고, 움직여도 너무 정밀한 드로잉이 필요하지 않기 때문에 프레임 수를 예를 들어 20개 정도로 적절히 줄일 수 있습니다. 이런 식으로 플레이어의 체력이 100에서 20으로 감소하면 50ms 이내에 뷰가 업데이트될 수 있으며 플레이어는 50ms 스위치를 느낄 수 없습니다. 체력 등 UI 레이어 데이터의 변화는 짧은 시간에 여러 번 연속적으로 변화하기 어렵고, 50ms의 지연은 인간이 인지하기 어렵기 때문에 빈번한 드로잉이 필요하지 않습니다. 초당 20프레임을 저장한다면 아마도 그림의 화면 영역 10개를 저장할 수 있을 것입니다.
다시 땅과 마찬가지로 지도는 플레이어가 움직일 때만 변경됩니다. 이런 방식으로 플레이어가 움직이지 않는 경우 프레임당 1개의 화면 영역을 저장할 수 있습니다. 플레이어가 움직일 때 부드러움을 보장해야 하기 때문에 지상의 최대 프레임 속도가 너무 낮으면 안 됩니다. 그라운드 프레임이 30프레임이라면 플레이어가 움직이지 않을 때 초당 30개의 화면 영역을 저장할 수 있습니다(이 프로젝트에서는 거의 화면을 채울 정도로 지도가 그려집니다). 또한, 다른 플레이어나 동물의 움직임으로 인해 지면이 바뀌지 않으며 지면 레이어를 다시 그릴 필요도 없습니다.
스프라이트 레이어의 최대 프레임 속도는 줄일 수 없습니다. 이 레이어는 캐릭터 이동과 같은 게임의 핵심 부분을 표시하므로 최대 프레임 속도는 40으로 설정됩니다.
이렇게 하면 초당 그려지는 영역이 됩니다. 플레이어가 영역을 이동할 때 화면은 80~100개일 수 있지만 플레이어가 움직이지 않을 때는 화면 영역이 50개만 있을 수 있습니다. 게임에서는 플레이어가 가만히 서서 몬스터와 싸우고, 아이템을 정리하고, 스킬을 공개하기 위해 멈춰서 있기 때문에 오랜 시간 동안 땅 뽑기가 발동되지 않아 성능이 크게 절약됩니다.
서버 측
js로 멀티플레이어 온라인 게임을 구현하는 것이 목표이기 때문에 서버는 Node를 사용하고 소켓을 사용하여 브라우저와 통신합니다. 이것의 또 다른 장점은 지도의 특정 좌표 지점에 장애물이 있는지 확인하는 것과 같은 일부 공통 논리를 양쪽 끝에서 재사용할 수 있다는 것입니다.
Node 측의 플레이어, 장면 등 게임 관련 데이터는 모두 메모리에 저장되며 정기적으로 파일에 동기화됩니다. 노드 서비스가 시작될 때마다 파일에서 메모리로 데이터를 읽어옵니다. 이와 같이 플레이어가 많아지면 파일 읽기 및 쓰기 빈도가 기하급수적으로 증가하여 성능 문제가 발생합니다. (나중에 안정성을 높이기 위해 파일 읽기 및 쓰기를 위한 버퍼를 추가했으며, 읽기 및 쓰기 과정에서 서버 재시작으로 인한 파일 손상을 방지하기 위해 "메모리-파일-백업" 방식을 사용했습니다.)
노드 측은 인터페이스, 데이터, 인스턴스 등 여러 계층으로 나누어져 있습니다. "인터페이스"는 브라우저와의 상호 작용을 담당합니다. "데이터"는 특정 약의 이름과 효과, 특정 몬스터의 속도와 체력 등의 정적 데이터이며 게임 규칙의 일부입니다. "인스턴스"는 게임의 현재 상태입니다. 예를 들어 특정 플레이어의 약은 "약물 데이터"의 인스턴스입니다. 또 다른 예를 들어, "사슴 인스턴스"에는 "현재 혈액량" 속성이 있습니다. 사슴 A는 10이고, 사슴 B는 14일 수 있으며, "사슴" 자체는 "초기 혈액량"만 갖습니다.
3. 장면 맵 구현
Map 장면
렌더링을 위해 여전히 Easycanvas를 사용하는 맵 장면 부분부터 시작해 보겠습니다.
Thinking
플레이어는 항상 화면 중앙에 고정되어 있기 때문에 플레이어의 움직임은 실제로 맵의 움직임과 같습니다. 예를 들어 플레이어가 왼쪽으로 달리면 지도가 오른쪽으로 이동합니다. 방금 말씀드린 것처럼 플레이어는 세 개의 캔버스 중 중간 레이어에 있고, 지도는 맨 아래 레이어에 속하므로 플레이어가 지도를 막아야 합니다.
합리적으로 보이지만 맵에 나무가 있으면 "플레이어의 레벨이 항상 나무보다 높다"는 것은 잘못된 것입니다. 현재로서는 두 가지 큰 솔루션이 있습니다.
지도 계층화와 "지상"과 "지상" 분리입니다. 아래 그림과 같이 땅이 왼쪽, 땅이 오른쪽이 되도록 두 레이어 사이에 플레이어를 배치한 다음 중간에 캐릭터를 끼워 겹쳐서 그립니다.
이렇게 하면 문제가 해결되는 것 같습니다. , 그러나 실제로는 2개의 새로운 항목을 소개합니다. 문제: 첫 번째는 플레이어가 때때로 "지상에 있는" 물체(예: 나무)에 의해 차단될 수 있고 때로는 "지상에 있는" 물체를 차단할 수 있어야 한다는 것입니다( 예를 들어, 나무 아래에 서 있으면 머리가 나무에 막히게 됩니다. 또 다른 문제는 렌더링 성능 비용이 증가한다는 것입니다. 플레이어는 항상 바뀌기 때문에 "그라운드" 레이어를 자주 다시 그려야 합니다. 이는 또한 원래 디자인을 깨뜨립니다. 즉, 큰 지상 지도의 렌더링을 최대한 저장하기 위해 캔버스의 레이어링을 더욱 복잡하게 만듭니다.
지도는 레이어링되지 않고 "지상"과 "지상"이 함께 그려집니다. 플레이어가 나무 뒤에 있을 때 다음 그림과 같이 플레이어의 투명도를 0.5로 설정합니다.
이 작업에는 단 한 가지 단점이 있습니다. 플레이어의 몸이 불투명하거나 반투명하므로(지도 위를 걷는 몬스터에게도 이 효과가 적용됨) 완전히 현실적이지는 않습니다. 왜냐하면 이상적인 효과는 플레이어의 신체 일부가 가려지는 장면을 만드는 것이기 때문입니다. 그러나 이는 성능 친화적이고 코드를 유지 관리하기 쉽습니다. 현재 이 솔루션을 사용하고 있습니다.
그렇다면 "지도" 그림의 어느 부분이 나무인지 어떻게 판단할 수 있을까요? 게임에는 일반적으로 0, 1, 2와 같은 숫자를 사용하여 통과할 수 있는 장소, 장애물이 있는 장소, 환승 지점 등을 식별하는 큰 지도 설명 파일(실제로 배열)이 있습니다. Legend of Hot Blood의 '설명 파일'은 가장 작은 단위로 48x32로 설명되므로 Legend에서 플레이어의 행동은 '체스판' 느낌을 갖게 됩니다. 단위가 작을수록 부드럽지만 차지하는 부피가 커지고 이 설명을 생성하는 프로세스에 더 많은 시간이 소요됩니다.
본론으로 들어가죠.
Implementation
친구에게 Legend of Legend 클라이언트의 "Beach Province" 지도 내보내기를 도와달라고 요청했습니다. 가로 33600, 세로 22400으로 내 컴퓨터 크기의 수백 배입니다. 컴퓨터가 폭발하는 것을 방지하려면 여러 개의 청크로 나누어 로드해야 합니다. 범례의 가장 작은 단위는 48x32이므로 지도를 480x320의 4900(70x70) 이미지 파일로 분할합니다.
캔버스 크기를 800x600으로 설정하여 플레이어가 캔버스 전체를 덮을 수 있는 총 3x3 그림만 로드하면 됩니다. 800/480=1.67인데 왜 2x2가 아닌가? 플레이어의 현재 위치로 인해 일부 사진만 표시될 가능성이 있기 때문입니다. 아래와 같습니다:
이 기사의 사례를 읽으신 후 방법을 마스터하셨다고 생각합니다. 더 흥미로운 정보를 보려면 PHP 중국어 웹사이트의 다른 관련 기사를 주목하세요!
추천 도서:
webpack-dev-server에서 원격 모드를 설정하는 방법
위 내용은 JS 모방 클래식 전설적인 게임의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!