[NEAR 101] 2편: 프론트엔드 연결하기 — 간단한 Clicker 게임 구현

Suji Yoon
DSRV
Published in
28 min readAug 8, 2022

[NEAR 101] 2편: 프론트엔드 연결하기 — 간단한 Clicker 게임 구현

DSRV Dev Guild에서는 더 많은 개발자들과 Web3 인프라를 만들어가기 위해, 다양한 메인넷과 스마트 컨트랙트에 대한 가이드를 연재합니다.

Disclaimer: 이 글은 정보 전달을 위한 목적으로 작성되었으며, 특정 프로젝트에 대한 투자 권고, 법률적 자문 등 목적으로 하지 않습니다. 모든 투자의 책임은 개인에게 있으며, 이로 발생된 결과에 대해 어떤 부분에서도 DSRV는 책임을 지지 않습니다. 본문이 포괄하는 내용들은 특정 자산에 대한 투자를 추천하는 것이 아니며, 언제나 본문의 내용만을 통한 의사결정은 지양하시길 바랍니다.

[NEAR 101 시리즈]

  1. Counter 컨트랙트 톺아보기
  2. 프론트엔드 연결하기 — 간단한 Clicker 게임 구현

지난 번 ‘[NEAR 101] 1편: NEAR Counter 컨트랙트 톺아보기’ 에서는 Rust와 AssemblyScript를 사용해서 Counter 컨트랙트를 함께 작성해보고 near-cli를 사용해서 컨트랙트를 직접 NEAR 테스트넷에 배포해보았습니다. 이번 시간에는 우리가 지난 시간에 배포한 컨트랙트와 프론트엔드가 통신하는 방법을 간단한 Clicker 게임 예제를 만들어보겠습니다.

소개

⭐️ 해당 문서는 NEAR 101 시리즈 1편을 읽은 독자 중 프론트엔드 작업을 하고 싶은 분들을 위한 문서로, 다음의 선수 지식을 필요로 합니다.1. [NEAR 101 시리즈 1편]
2. JavaScript 문법에 대한 기본적인 이해
3. React.js 코드에 대한 기본적인 이해

이번 튜토리얼에서는 간단한 Clicker 게임을 만들어 보면서 다음 내용을 학습하겠습니다.

  • near-api-js의 사용법: 이더리움 스마트 컨트랙트와 프론트엔드가 통신할 때 Web3.jsEther.js를 사용합니다. NEAR에서는 스마트 컨트랙트와 프론트엔드가 통신할 때 near-api-js 를 사용합니다.

NEAR에서는 프론트엔드와 통신하는 과정을 돕는 JavaScript API인 near-api-js 를 제공하는데요. 본 글에서는 ^0.45.1 버전을 사용합니다.

우리가 이번에 실습해 볼 Clicker 예제는, 15초 동안 화면에 나타나는 NEAR 아이콘을 클릭하여 점수를 얻고, 게임이 종료된 후 컨트랙트에 트랜잭션을 보내 점수를 저장하는 게임입니다.

다음 링크에서 게임을 직접 플레이할 수 있습니다.

구현 요구사항

구현 단계는 크게 세 단계로 나눌 수 있으며 각각의 단계에서 구현해야 할 주요 요구 사항은 다음과 같습니다.

1단계: 프론트엔드와 스마트 컨트랙트 연결하기

  • 요구사항 1: NEAR 테스트넷 환경을 설정하고 연결할 수 있다.
  • 요구사항 2: 테스트넷에 배포한 스마트 컨트랙트와 연결할 수 있다.
  • 요구사항 3: 컨트랙트와 통신하는 함수를 작성할 수 있다.

2단계: 메인 화면 구현하기

  • 요구사항 1: CONNECTDISCONNECT 버튼의 UI 컴포넌트를 구현하고, 버튼을 클릭했을 때 지갑을 연결하고 연결을 해지할 수 있다.
  • 요구사항 2: 지갑에 연결되었을 때 잔액을 표시할 수 있다.
  • 요구사항 3: PLAY 버튼을 눌렀을 때 /play 주소로 라우팅할 수 있다.

3단계: Clicker 게임 구현하기

  • 요구사항 1: 플레이 화면 UI 를 구현할 수 있다.
  • 요구사항 2: Game Start 버튼을 눌러 get_numreset 메소드를 순차적으로 실행할 수 있다.
  • 요구사항 3: 게임이 시작되면 15초 동안 화면에 랜덤하게 나타나는 NEAR 아이콘을 클릭하여 점수를 획득할 수 있다.
  • 요구사항 4: 게임 종료 후 나타나는 Transaction 버튼의 UI 컴포넌트를 구현하고, 이를 클릭하면 increment 메소드를 실행할 수 있다.

구현 시작하기

다음의 명령어를 사용하여 로컬에 저장소를 clone 받아주세요.

git clone https://github.com/DSRV-DevGuild/near-clicker-game.git
cd near-clicker-game && yarn install

우리는 main 브랜치에서 아래의 단계들을 따라가며 함께 Clicker 게임을 구현할 것입니다. 각각의 단계는 브랜치별로 정리되어 있으며 단계별 요구사항은 커밋 로그를 통해 확인할 수 있습니다.

1단계 — 프론트엔드와 스마트 컨트랙트 연결하기

요구사항 1: NEAR 테스트넷 환경을 설정하고 연결할 수 있다.

📝 작업 포인트

[✓] NEAR 테스트넷 환경 설정 파일 만들기
[ ] NEAR 테스트넷 연결하기
[ ] NEAR 지갑 연결하기

웹 프론트엔드와 NEAR 스마트 컨트랙트가 통신하기 위해서는 먼저 네트워크에 연결해야 합니다. 그리고 네트워크에 연결하기 위해서는 연결할 네트워크의 정보가 필요합니다.

src/near/config.js 파일을 생성해서 연결할 네트워크의 환경 설정 정보를 따로 관리하도록 하겠습니다. 먼저 아래와 같이 배포한 컨트랙트의 아이디를 가져와서 변수에 넣어줍니다. 루트 디렉토리에 .env 파일을 생성해서 관리하거나 config.js 파일에서 바로 아이디를 입력할 수 있습니다.

src/near/config.js 출처: Yoon-Suji of DSRV
.env 출처: Yoon-Suji of DSRV

다음으로 네트워크의 환경 설정 정보를 입력하도록 하겠습니다. 네트워크에 연결할 때 필요한 정보는 near-api-jsConnectConfig 인터페이스로 구현되어 있습니다. 아래 코드는 공식 문서를 기준으로 작성했습니다.

  • networkId: 연결하고자 하는 네트워크의 아이디를 입력합니다.
  • nodeUrl: 네트워크의 RPC API 주소를 입력합니다. 우리는 AllThatNode에서 제공하는 RPC Node를 사용할 것입니다. NEAR 101 1편을 참고해서 API KEY를 발급 받은 후, RPC Node 주소 뒤에 입력해주세요.
  • contractName: 네트워크에 배포한 컨트랙트의 아이디를 입력합니다.
  • walletUrl: 사용자를 지갑으로 리다이렉트하는 데 사용되는 NEAR wallet 주소입니다.
  • helperUrl: 마스터 계정이 제공되지 않을 경우 계정을 생성해주는 NEAR Contract Helper 주소입니다.
  • exploreUrl: NEAR Explorer 주소입니다.
  • keyStore: 트랜잭션에 서명을 하기 위해서는 지갑의 정보를 저장하는 keyStore 객체가 필요합니다. 아래 코드의 경우에는 브라우저의 로컬 스토리지를 사용합니다.
src/near/config.js

📝 작업 포인트

[✓] NEAR 테스트넷 환경 설정 파일 만들기
[✓] NEAR 테스트넷 연결하기
[ ] NEAR 지갑 연결하기

필요한 네트워크의 정보를 입력했으므로, 이제 near-api-jsconnect 모듈을 사용해서 네트워크에 연결합니다. config.js 와 같은 위치에 utils.js 파일을 생성해주세요. connect 함수에 config.js에서 선언한 connectionConfig를 인자로 전달해서 NEAR 테스트넷 네트워크에 연결할 수 있습니다.

src/near/utils.js 출처: Yoon-Suji of DSRV

📝 작업 포인트

[✓] NEAR 테스트넷 환경 설정 파일 만들기
[✓] NEAR 테스트넷 연결하기
[✓] NEAR 지갑 연결하기

요구사항 1–1의 완성본을 보고 싶으시면 ➡️ git checkout 2f502ec

다음으로 NEAR에서 지원하는 브라우저 지갑과 연결해서 계정의 정보를 가져오기 위한 설정을 해봅시다. WalletConnection 함수를 이용하면 NEAR 지갑과 상호작용하는 객체를 생성할 수 있습니다. 앞서 keyStore 값을 BrowserLocalStorageKeyStore 로 설정했기 때문에 Key는 로컬 스토리지에 저장됩니다.

WalletConnectiongetAccountId 메소드를 이용해서 계정 아이디를 가져올 수 있는데, 사용자에게 권한을 받지 않은 경우에는 빈 문자열이 반환됩니다.

src/near/utils.js 출처: Yoon-Suji of DSRV

메소드가 정상적으로 동작하는 지 알아보기 위해 아래의 코드를 initContract 함수에 추가하고 src/index.js 에서 initContract를 실행시켜 봅시다.

src/near/utils.js 출처: Yoon-Suji of DSRV
src/index.js 출처: Yoon-Suji of DSRV

yarn start 혹은 npm start를 통해 프로젝트를 실행시키면 아래와 같이 콘솔창에 출력될 것입니다. 아직 사용자에게 지갑에 접근하는 권한을 요청하지 않았기 때문에 window.accountId는 빈 문자열이 출력됩니다.

💡 // (debug) 주석이 달린 코드는 요구사항이 잘 구현되었는 지 확인한 후에 삭제해주세요.
요구사항 1–1 구현 화면. 출처: DSRV

요구사항 2: 테스트넷에 배포한 스마트 컨트랙트와 연결할 수 있다.

📝 작업 포인트

[✓] 컨트랙트 불러오기

요구사항 1–2의 완성본을 보고 싶으시면 — git checkout c290999

near-api-jsContract 클래스를 사용해서 테스트넷에 배포한 스마트 컨트랙트를 불러와서 연결할 수 있습니다. 이더리움에서 스마트 컨트랙트와 통신할 때 ABI를 사용하는 것처럼, NEAR에서는 Contract 객체를 생성할 때 ABI와 같은 통신 인터페이스를 정의합니다.

통신 인터페이스는 컨트랙트에서 사용하고자 하는 메소드를 컨트랙트의 상태를 변화시키지 않는 viewMethods와 컨트랙트의 상태를 변화시키는 changeMethods로 나누어 정의할 수 있습니다.

window.walletConnection.account() 메소드는 지갑에 연결된 NEAR 계정을 반환합니다. 이 때, 연결된 계정은 changeMethods를 실행하는 트랜잭션에 서명하는 역할을 합니다.

src/near/utils.js 출처: Yoon-Suji of DSRV

여기까지 구현을 완료했다면 yarn start를 통해 프로젝트를 실행시킨 후 개발자 도구 Console 창에 다음을 입력해봅니다.

window.contract.get_num({args:{}}).then(console.log)

만약 개발자 도구 Console 창이 컨트랙트의 카운터 값을 출력한다면 연결에 성공한 것입니다. viewMethods는 컨트랙트의 상태를 변화시키지 않습니다. 따라서 viewMethods 는 지갑에 사용자가 로그인하지 않고도 실행시킬 수 있습니다.

요구사항 1–2 구현 화면. 출처: DSRV

요구사항 3: 컨트랙트와 통신하는 함수를 작성할 수 있다.

📝 작업 포인트

[✓] get_num 메소드를 실행해서 컨트랙트의 현재 count 값 조회하기
[ ]
increment 메소드를 실행해서 컨트랙트의 count 값에 원하는 값을 더하기
[ ]
reset 메소드를 실행해서 컨트랙트의 count 값 0으로 초기화하기
[ ] 지갑에 로그인하고 로그아웃하는
login, logout 함수 작성하기

이제 컨트랙트와 통신하는 함수를 작성해보도록 하겠습니다. src/near/utils.js 안에 get_num 이라는 함수를 아래와 같이 작성해주세요. get_numviewMethods로 컨트랙트의 현재 count 값을 반환합니다.

src/near/utils.js 출처: Yoon-Suji of DSRV

📝 작업 포인트

[✓] get_num 메소드를 실행해서 컨트랙트의 현재 count 값 조회하기
[✓] increment 메소드를 실행해서 컨트랙트의 count 값에 원하는 값을 더하기
[ ]
reset 메소드를 실행해서 컨트랙트의 count 값 0으로 초기화하기
[ ] 지갑에 로그인하고 로그아웃하는
login, logout 함수 작성하기

다음으로 컨트랙트의 상태를 변경하는 changeMethods인 increment 메소드를 실행시키는 함수를 작성하겠습니다. increment 는 매개변수로 count 값을 전달해서 컨트랙트의 count 값에 더해줍니다.

src/near/utils.js 출처: Yoon-Suji of DSRV

📝 작업 포인트

[✓] get_num 메소드를 실행해서 컨트랙트의 현재 count 값 조회하기
[✓]
increment 메소드를 실행해서 컨트랙트의 count 값에 원하는 값을 더하기
[✓] reset 메소드를 실행해서 컨트랙트의 count 값 0으로 초기화하기
[ ] 지갑에 로그인하고 로그아웃하는
login, logout 함수 작성하기

reset 은 컨트랙트의 count 값을 0으로 초기화하는 메소드입니다. increment와 같이 changeMethods에 속합니다. 다음과 같이 실행할 수 있습니다.

src/near/utils.js 출처: Yoon-Suji of DSRV

📝 작업 포인트

[✓] get_num 메소드를 실행해서 컨트랙트의 현재 count 값 조회하기
[✓]
increment 메소드를 실행해서 컨트랙트의 count 값에 원하는 값을 더하기
[✓]
reset 메소드를 실행해서 컨트랙트의 count 값 0으로 초기화하기
[✓] 지갑에 로그인하고 로그아웃하는 login, logout 함수 작성하기

요구사항 1–3의 완성본을 보고 싶으시면 — git checkout 1527d16

앞서 설명한 것처럼 viewMethods는 사용자가 지갑에 로그인하지 않고도 메소드를 호출할 수 있지만 changeMethods는 트랜잭션에 서명을 해 줄 계정이 필요합니다.

이제 지갑에 연결해서 계정의 정보를 가져오는 login 함수와, 연결을 해지하는 logout 함수를 구현해봅시다.

WalletConnection 객체의 requestSignIn 함수에 연결하고자 하는 컨트랙트의 아이디를 인자로 넣어서 요청하면 사용자를 NEAR 지갑의 인증 페이지로 Redirect 합니다. 사용자가 요청을 허락하면 access key가 생성되고 생성된 키는 브라우저의 로컬 스토리지에 저장됩니다.

지갑의 연결을 해지하고 싶다면 signOut 메소드를 호출하면 됩니다. 연결을 해지한 후에는 페이지를 다시 로드해서 변경사항을 적용합니다.

src/near/utils.js 출처: Yoon-Suji of DSRV

이제 src/index.js 에서 함수가 잘 동작하는지 실행해보도록 하겠습니다. 그러기 위해서는 먼저 initContract 함수의 실행을 통해 window.contract 변수가 정의되어 있어야 합니다. 따라서 다음과 같이 initContract 함수가 실행 완료된 후에 App이 렌더링 되도록 코드를 변경하고 reset 메소드를 호출해보겠습니다.

src/index.js 출처: Yoon-Suji of DSRV

yarn start 를 통해 프로젝트를 실행시키면 다음과 같은 에러가 발생할 것입니다. 지갑에 연결하지 않아서 account_id 값을 가져오지 못했는데 changeMethods를 실행했기 때문에 발생하는 에러입니다.

changeMethods 실행 시 에러 발생 화면. 출처: DSRV

Console 창에서 window.walletConnection.requestSignIn(string: contractName) 을 실행하면 NEAR Wallet 페이지로 Redirect 된 후, Next와 Accept 버튼을 차례로 클릭합니다. 이후 페이지를 다시 로드하면 콘솔 창에 reset 트랜잭션이 실행된 결과가 출력됩니다.

NEAR Wallet 연결 화면 — 1. 출처: DSRV
NEAR Wallet 연결 화면 — 2. 출처: DSRV
요구사항 1–3 구현 화면. 출처: DSRV

지금까지 구현한 src/near/utils.js 의 전체 코드는 다음과 같습니다.

src/near/utils.js 출처: Yoon-Suji of DSRV

2단계 — 메인 화면 구현하기

이번에는 src/App.js 에서 메인 화면을 구현해보도록 하겠습니다. 우리가 최종적으로 구현할 메인 화면은 다음과 같습니다.

메인 화면 — 지갑에 연결하기 전. 출처: DSRV
메인 화면 — 지갑에 연결한 후. 출처: DSRV

요구사항 1: CONNECT 와 DISCONNECT 버튼의 UI 컴포넌트를 구현하고, 버튼을 클릭했을 때 지갑을 연결하고 연결을 해지할 수 있다.

📝 작업 포인트

[✓] 지갑 연결 여부에 따라서 DISCONNECT와 CONNECT 버튼 나타내기
[ ]
CONNECT 버튼을 눌렀을 때 지갑이 연결되고 DISCONNECT 버튼을 눌렀을 때 지갑과 연결이 해지되도록 구현하기

src/near/utils.js 에서 우리는 WalletConnection.getAccountId() 메소드를 이용해서 지갑에 연결된 경우에 계정의 아이디를 가져와서 window.accountId 에 저장하도록 했습니다. 따라서 다음과 같이 window.accountId 값이 존재하는 지에 따라 DISCONNECTCONNECT 버튼을 표시할 수 있습니다.

src/App/js 출처: Yoon-Suji of DSRV

📝 작업 포인트

[✓] 지갑 연결 여부에 따라서 DISCONNECTCONNECT 버튼 나타내기
[✓] CONNECT 버튼을 눌렀을 때 지갑이 연결되고 DISCONNECT 버튼을 눌렀을 때 지갑과 연결이 해지되도록 구현하기

요구사항 2–1의 완성본을 보고 싶으시면 — git checkout b929104

이제 src/near/utils.js 에서 구현한 login, logout 메소드를 import 합니다. 그리고, 각각 CONNECTDISCONNECT 버튼을 클릭할 때 해당 메소드를 실행하도록 onClick 메소드로 추가합니다.

src/App.js 출처: Yoon-Suji of DSRV

여기까지 구현한 후에 프로젝트를 실행시키면 다음과 같은 화면이 표시됩니다. 현재 지갑에 로그인되어 있는 상태이기 때문에 DISCONNECT 버튼이 표시됩니다. 버튼을 눌러 로그아웃과 로그인 기능이 동작하는 지 확인합니다.

요구사항 2–1 구현 화면. 출처: DSRV

요구사항 2–1의 완성된 src/App.js 전체 코드는 다음과 같습니다.

src/App.js 출처: Yoon-Suji of DSRV

요구사항 2: 지갑에 연결되었을 때 잔액을 표시할 수 있다.

📝 작업 포인트

[✓] 지갑의 잔액 가져오는 함수 구현하기
[ ] 지갑에 연결되었을 때 잔액이 나타나도록 구현하기

이제 지갑에 연결되었을 때 계정의 잔액을 화면에 출력해보겠습니다. WalletConnection.account().getAccountBalance() 메소드를 이용해서 계정의 잔액을 가져올 수 있습니다. 해당 메소드는 다음과 같은 AccountBalance 타입을 리턴합니다.

Interface: AccountBalance 예제. 출처: Yoon-Suji of DSRV

위의 Interface에서 정의된 프로퍼티 중 total에 해당하는 값을 가져올 것입니다. 해당 total 프로퍼티는 이더리움에서의 Wei와 같은 최소 단위로 표현되어 있습니다. 이를 NEAR 단위로 바꾸기 위해 formatNearAmount라는 함수를 이용해서 소수점 둘째 자리까지 반올림하여 나타내겠습니다.

src/near/utils.js 에 다음의 코드를 추가해주세요.

src/near/utils.js 출처: Yoon-Suji of DSRV

다시 src/App.js 파일로 돌아와서 위에서 정의한 accountBalance를 import하고 잔액을 저장할 변수를 useState 메소드를 이용하여 관리합니다. 지갑에 연결된 후에 계정의 잔액을 가져올 수 있으므로 useEffect를 활용하여 연결된 계정이 바뀔 때마다 accountBalance 를 호출하도록 구현합니다.

src/App.js 출처: Yoon-Suji of DSRV

📝 작업 포인트

[✓] 지갑의 잔액 가져오는 함수 구현하기
[✓] 지갑에 연결되었을 때 잔액이 나타나도록 구현하기

요구사항 2–2의 완성본을 보고 싶으시면 — git checkout 00b1c2d

다음과 같이 코드를 작성하여 지갑에 연결된 경우에만 잔액 정보를 표시하도록 구현합니다.

src/App.js 출처: Yoon-Suji of DSRV

여기까지 구현이 완료되었다면, yarn start 또는 npm start 로 프로젝트를 실행했을 때 다음과 같은 화면이 나타날 것입니다. DISCONNECT 버튼을 누르면 accountbalance 정보가 사라집니다.

요구사항 2–2 구현 화면. 출처: DSRV

요구사항 2–2의 완성된 src/App.js 전체 코드는 다음과 같습니다.

src/App.js 출처: Yoon-Suji of DSRV

요구사항 3: PLAY 버튼을 눌렀을 때 /play 주소로 라우팅할 수 있다.

📝 작업 포인트

[✓] 지갑에 연결되었을 때 PLAY 버튼이 나타나도록 구현하기
[ ]
PLAY 버튼을 눌렀을 때 /play 주소로 연결하기
[ ] 라우터 추가하기

이번에는 style 속성의 visibility 태그를 사용해서 지갑과 연결된 경우에만 PLAY 버튼이 나타나도록 구현해보겠습니다. visible 변수를 만들어서 지갑과 연결되었을 때는 visible, 지갑과 연결되지 않았을 때는 hidden 값이 할당되도록 구현합니다.

src/App.js 출처: Yoon-Suji of DSRV

📝 작업 포인트

[✓] 지갑에 연결되었을 때 PLAY 버튼이 나타나도록 구현하기
[✓] PLAY 버튼을 눌렀을 때 /play 주소로 연결하기
[ ] 라우터 추가하기

useNavigate 메소드를 사용하면 navigate 기능을 구현할 수 있습니다. PLAY 버튼을 누르면 /play 주소로 이동하도록 버튼의 onClick 속성에 아래와 같이 추가합니다.

src/App.js 출처: Yoon-Suji of DSRV

📝 작업 포인트

[✓] 지갑에 연결되었을 때 PLAY 버튼이 나타나도록 구현하기
[✓]
PLAY 버튼을 눌렀을 때 /play 주소로 연결하기
[✓] 라우터 추가하기

요구사항 2–3의 완성본을 보고 싶으시면 — git checkout 8d693db

마지막으로 /play 주소로 접속하면 플레이 화면이 표시되도록 Router를 추가해야 합니다. 먼저 플레이 화면을 위해 src/pages 디렉토리를 생성한 후, 해당 디렉토리에 play.js 파일을 생성합니다.

플레이 화면은 다음 단계에서 구현할 것이므로 지금은 빈 함수만 선언합니다.

src/pages/play.js 출처: Yoon-Suji of DSRV

src/index.js 파일에서 아래와 같이 Router를 추가합니다.

src/index.js 출처: Yoon-Suji of DSRV

여기까지 구현이 완료되었다면 프로젝트를 실행했을 때 다음과 같이 PLAY 버튼이 표시됩니다. 해당 PLAY 버튼을 클릭하면 /play 주소로 이동하며, 요구사항 2-3 구현 화면 — play 화면 이 나타날 것입니다.

요구사항 2–3 구현 화면 메인 화면. 출처: DSRV
요구사항 2–3 구현 화면 play 화면. 출처: DSRV

요구사항 2–3까지 구현한 src/App.js 전체 코드는 다음과 같습니다.

src/App.js 출처: Yoon-Suji of DSRV

3단계 — Clicker 게임 구현하기

이제 src/pages/play.js 에서 플레이 화면을 구현할 것입니다. 최종 플레이 페이지는 다음과 같습니다.

최종 play 화면. 출처: DSRV

요구사항 1: 플레이 화면 UI 를 구현할 수 있다.

💡 체크 포인트

[✓] 플레이 화면 UI 구현하기
[ ]
useState 로 변수 상태 관리하기

이번 요구사항에서는 플레이 화면 UI 를 구현하겠습니다. 플레이 화면 UI는 Previous ScoreCurrent Score를 좌측 상단에 표시하고, 우측 상단에는 남은 시간을 보여줍니다. 중앙에는 GAME START 버튼이 있고 게임이 종료되면 TRANSACTION 으로 버튼이 전환됩니다.

아이콘은 게임 컨테이너 안에 존재하고 게임이 시작될 때 나타나며 게임이 종료되면 사라집니다. 트랜잭션이 실행되는 동안에는 “Loading…” 을 보여주도록 합니다. play.js 파일의 Play 함수 안에서 다음 코드를 추가합니다.

src/pages/play.js 출처: Yoon-Suji of DSRV

💡 체크 포인트

[✓] 플레이 화면 UI 구현하기
[✓] useState 로 변수 상태 관리하기

요구사항 3–1의 완성본을 보고 싶으시면 — git checkout 61adaca

위의 코드를 저장한 후 실행해보면, 변수가 지정되지 않았다는 오류를 마주하게 됩니다. 변수를 선언합시다.

playTime은 15초로 지정해주고, 그 외의 변수들은 useState 메소드를 사용하여 상태를 관리합니다.

src/pages/play.js 출처: Yoon-Suji of DSRV

프로젝트를 실행하고 PLAY 버튼을 눌렀을 때 아래와 같은 화면이 나타난다면 여기까지 구현에 성공한 것입니다.

요구사항 3–1 구현 화면. 출처: DSRV

요구사항 3–1 까지 구현한 src/pages/play.js 전체 코드는 다음과 같습니다.

src/pages/play.js 출처: Yoon-Suji of DSRV

요구사항 2: Game Start 버튼을 눌러 get_numreset 메소드를 순차적으로 실행할 수 있다.

💡 체크 포인트

[✓] GAME START 버튼을 눌렀을 때 get_num, reset 메소드 실행하기

요구사항 3–2의 완성본을 보고 싶으시면 — git checkout 5fa4264

이번 요구사항에서는 GAME START 버튼을 클릭했을 때 실행할 메소드를 작성합니다. 앞서 src/near/utils.js 에 작성한 get_num 메소드를 이용하여 컨트랙트의 count 값을 조회하고, previousScore 에 저장합니다. 이후 reset 메소드를 이용해 컨트랙트의 count 값을 0으로 초기화합니다.

컨트랙트와의 통신이 끝나면 게임을 시작합니다. 게임이 시작되면 15초로 설정되어있던 time이 1초마다 1씩 줄어들어야 합니다. 해당 기능을 구현하기 위해 setInterval 메소드를 사용합니다.

src/pages/play.js 출처: Yoon-Suji of DSRV
💡 DSRV’s Tip: setInterval은 어떻게 사용할까?

setTimeout이 일정시간이 지난 후 함수를 한 번 실행시키는 것과 달리 setInterval은 함수를 일정한 간격을 두고 주기적으로 실행합니다.
setInterval은 해당 타이머의 식별자인 timerId를 반환하고 clearInterval(timerId)를 이용해 함수의 실행을 멈출 수 있습니다.

이제 플레이 화면에서 GAME START 버튼을 클릭하여 get_num을 통해 조회한 현재 컨트랙트의 count 값이 Previous Score에 표시되고 reset 트랜잭션이 실행됩니다. 트랜잭션 실행이 완료되면 Console 창에 다음과 같은 로그가 출력될 것입니다.

요구사항 3–2 구현 화면. 출처: DSRV

요구사항 3–2까지 구현한 src/pages/play.js 전체 코드는 다음과 같습니다.

src/pages/play.js 출처: Yoon-Suji of DSRV

요구사항 3: 게임이 시작되면 15초 동안 화면에 랜덤하게 나타나는 NEAR 아이콘을 클릭하여 점수를 획득할 수 있다.

💡 체크 포인트

[✓] NEAR 아이콘을 클릭하면 현재 점수를 1씩 증가시키고, 다음 위치를 랜덤하게 지정한다.
[ ] 15초가 지나서
time 이 0이 되면 게임 종료한다.

이번 요구사항에서는 NEAR 아이콘을 클릭하면 점수를 1씩 증가하고 다음 위치를 랜덤하게 지정하는 메소드를 구현합니다. 15초가 모두 지나 시간이 0이 되면 게임이 종료되도록 구현합니다.

먼저 NEAR 아이콘을 클릭했을 때 실행할 메소드를 작성합니다. setScore 를 이용해 현재 점수를 1씩 증가시키고, setTargetPositionMath.random 메소드를 이용해서 아이콘의 위치를 랜덤으로 설정합니다.

src/pages/play.js 출처: Yoon-Suji of DSRV

💡 체크 포인트

[✓] NEAR 아이콘을 클릭하면 현재 점수를 1씩 증가시키고, 다음 위치를 랜덤하게 지정한다.
[✓] 15초가 지나서 time 이 0이 되면 게임 종료한다.

요구사항 3–3의 완성본을 보고 싶으시면 — git checkout 594cf8a

다음으로 1초마다 1씩 줄어드는 time 값이 0이 될 때 게임이 종료되도록 하는 코드를 구현합니다.

time 값이 변하는 것을 감지하는 useEffect 함수를 이용해서 time이 0이 될 때 아이콘이 사라지도록 설정하고 게임 종료 알람창을 표시합니다.

그리고 clearInterval 메소드를 이용해 계속 실행되고 있는 setInterval 함수를 중지합니다.

src/pages/play.js 출처: Yoon-Suji of DSRV

이제 플레이 화면에서 GAME START 버튼을 눌렀을 때 나타나는 NEAR 아이콘을 클릭하면 점수가 올라갑니다. 또한, NEAR 아이콘의 위치가 랜덤하게 표시되는 것을 확인할 수 있습니다. 시간이 0초가 되면 아래와 같이 알람창이 뜨면서 아이콘이 사라집니다.

요구사항 3–3 구현 화면. 출처: DSRV

요구사항 3–3까지 구현한 src/pages/play.js 전체 코드는 다음과 같습니다.

src/pages/play.js 출처: Yoon-Suji of DSRV

요구사항 4: 게임 종료 후 나타나는 Transaction 버튼의 UI 컴포넌트를 구현하고, 이를 클릭하면 increment 메소드를 실행할 수 있다.

💡 체크 포인트

[✓] TRANSACTION 버튼을 눌렀을 때 increment 메소드 실행하기

요구사항 3–4의 완성본을 보고 싶으시면 — git checkout dd22d4d

이번 요구사항에서는 게임이 종료한 후에 TRANSACTION 버튼을 클릭했을 때, increment 메소드를 실행해서 컨트랙트의 count 값에 더하는 기능을 구현하겠습니다. 앞선 요구사항 2번에서 reset 메소드를 통해 컨트랙트의 count 값을 0으로 초기화했습니다. 따라서 increment 메소드를 실행하면 컨트랙트의 count 값이 사용자가 획득한 점수로 기록됩니다.

먼저 TRANSACTION 버튼을 클릭했을 때 실행되는 submitScore 메소드를 작성합니다. 사용자가 얻은 점수인 score 만큼 increment를 실행합니다. increment 트랜잭션이 완료되면 get_num 메소드를 통해 컨트랙트의 count 값을 다시 조회하여 previousScore에 업데이트 합니다.

컨트랙트와의 모든 통신이 끝난 후에는 게임을 다시 시작할 수 있도록 gameOver 값을 false로 설정하고 0이 된 time 을 다시 15초로 바꿔줍니다.

src/pages/play.js 출처: Yoon-Suji of DSRV
요구사항 3–4 구현 화면. 출처: DSRV

요구사항 3–4까지 구현한 src/pages/play.js 전체 코드는 다음과 같습니다.

src/pages/play.js 출처: Yoon-Suji of DSRV

완성된 전체 코드는 다음 저장소의 Step3 브랜치에서 확인할 수 있습니다.

글을 마무리하며

이번 시간에는 간단한 Clicker 게임을 통해 NEAR 테스트넷에 배포한 컨트랙트와 프론트엔드가 통신하는 방법을 알아보았습니다. NEAR 컨트랙트는 near-api-js를 지원하기 때문에 자바스크립트를 이용하여 프론트엔드와 통신할 수 있습니다.

이로써 “1편: NEAR Counter 컨트랙트 톺아보기”, “2편: 프론트엔드 연결하기 — 간단한 Clicker 게임 구현” 에 이르는 NEAR 101 시리즈가 마무리되었습니다.

이 글이 NEAR 공부를 시작하고자 했던 많은 개발자분들께서 NEAR에 쉽게 입문하는데 도움이 되었길 바라며, 다음 글로 또 찾아오도록 하겠습니다. 이 글을 읽는데 귀중한 시간을 할애해주셔서 감사합니다.

Author
Suji Yoon of DSRV, Developer Evangelist Intern (Twitter @suji_forcrypto)

Reviewed by
Sigrid Jin of DSRV, Technical Writer & Developer Evangelist (Twitter @sigridjin_eth)
Owen Hwang of DSRV, Research Manager (Twitter @owenhwang_dsrv)

--

--

Suji Yoon
DSRV
Writer for

Software Engineer Intern @DSRV / Ewhachain / Twitter: @suji_forcrypto