캐릭터 장비 데이터를 모두 불러왔으니 이제 장비 정보창을 만들어보자.

api에서 캐릭터 장비 정보를 불러올 때 해당 캐릭터가 장착하지 않은 장비는 장비칸 정보가 아예 누락되어 오기 때문에 미리 캐릭터가 장비할 수 있는 장비 객체를 만들어 정보를 정리했다.

그리고 해당 정보를 GearSlot 컴포넌트에 prop으로 넘겨주었다.

자꾸 실수하는 부분이 있는데, 변수안의 정보가 바뀔 수 있는 경우에는 computed로 관리해주어야하는데, 자꾸 data에 넣어놓는다... data에 들어간 변수는 페이지에서 다른 캐릭터를 검색했을 경우 해당 정보가 바뀌질 않는다. 주의하자.

장착한 장비의 등급과 아이템 레벨에 따라 표기를 다르게 해주었다.

장비 8가지 정도 다른 등급이 있는데, 다른 컴포넌트에서도 사용될 예정이기 때문에 state에 저장해두었다.

장비칸에 마우스를 호버링해 장비의 세부 정보를 볼 수 있게 만들었다. 해당 기능은 v-tooltip을 이용했다.

아이템의 세부 정보와 착용효과 등 모든 정보는 장비 데이터를 가져올때 함께 가져왔기 떄문에 그대로 넣어주기만 하면 된다. 

이것으로 캐릭터 장비 정보창을 마무리했다.

이번에는 해당 캐릭터의 착용 아이템을 나타내는 컴포넌트를 만들어보겠다.

우선, 검색한 캐릭터의 착용 아이템 정보를 가져온다.

api에서 불러올 때 착용하지 않은 아이템의 부위는 아예 정보가 누락되어서 기본 틀이 되어주는 객체를 만들어 그 안에 채우는 식으로 진행하였다.

그 다음, 위에서 가져온 데이터에 포함된 각 아이템의 ID를 이용해 아이템의 이미지를 가져온다.

불러온 이미지는 기존에 불러온 장비 데이터 객체에 넣어줄것이다.

 

해당 코드 작업중에 I/O함수때문에 자꾸 에러가 나서... 모든 액션을 waterfall과 auto로 교통정리를 했다.

불필요하게 별도 파일로 구분해놓았던 액션들을 통합했다.

 

기존에 파일단위로 분리되어있던 액션들을 실행 시점에 따라 모아주었다. 

waterfall중간에 for문으로 io함수를 처리할때 every를 사용해 해당 반복문이 모두 끝나고 다음 콜백을 넘길수 있도록 했다.

이번에는 캐릭터의 공격대 진행 상황을 나타내는 컴포넌트를 만들것이다.

우선 캐릭터의 던전 진척도를 api를 통해 가져온다.

데이터를 가져오면서 내가 쓰기 편한 형태로 가공해주었다.

비교적 최근 확장팩의 레이드와 그 이전의 확장팩 레이드가 난이도가 다르게 표기되는 부분이 있었다.

현재는 LFR(공격대 찾기), NORMAL(일반), HEROIC(영웅), MYTHIC(신화)로 구분되는데, 이전 일부 확장팩에서는 LEGACY_10_MAN 이런식으로 표기되어있어 이부분은 현재 확장팩의 난이도에 맞게 바꾸어주었다.

진척도 카드에 들어갈 이미지도 같이 불러올것이다.

이제 v-card를 이용해 공격대 진행도를 나타내는 카드를 만들것이다

RaidProgressCard 컴포넌트로 v-card 틀을 만들고, ExpansionSort 컴포넌트로 확장팩별로 정리를 해주고, RaidProgressIter 컴포넌트로 확장팩별로 카드들을 출력했다. 결과는 다음과 같다.

각 컴포넌트간에 데이터를 넘겨줄때는 props를 이용하면 된다.

이 부분을 작업하면서 신경쓰였던 부분이 이미지가 전부 불러와지기 전에 컴포넌트가 구성되어서 그런지 이미지가 누락되는 문제가 있었다. 그래서 페이지가 구성될 때 미리 이미지를 불러오는 식으로 순서를 바꾸어주었다.

async 모듈의 auto를 이용해 교통정리를 좀 해줬다.

auto를 사용하면서 잘 안돼서 골머리를 좀 썩혔는데, 알고보니 action에서 사용된 IO함수때문에 문제가 발생했던것이다. 떄문에 accessToken을 dispatch할떄 promise를 리턴하게 하고 .then()을 붙여주었다. 

이번엔 네임 플레이트에 들어갈 캐릭터 썸네일을 가져올것이다.

근데 이전에 만들어둔 썸네일을 가져오는 함수가 잘 작동하다가 오류가 발생해 자세히 보니 최근 확장팩 업데이트를 하면서 api의 형식이 조금 바뀌어서 최근 플레이를 한 캐릭터를 검색할 경우 썸네일이 제대로 불러와지지 않는 문제가 있었다.

따라서 이 부분에 대해서 예외처리를 해줄 필요가 생겼다.

export function getCharacterMedia(store){
  this.$axios.$get(`https://kr.api.blizzard.com/profile/wow/character/${store.state.realmSlug}/${store.state.characterName}/character-media`,{
    params:{
      region:'kr',
      namespace:'profile-kr',
      locale:'en_US',
      access_token: store.state.accessToken
    }
  })
    .then(function (response) {
      console.log(response)
      if(response.hasOwnProperty('assets')){
        store.commit('setCharacterMedia',getCharacterMediaUrl_New(response))
      }
      else{
        store.commit('setCharacterMedia',getCharacterMediaUrl_Old(response))
      }
    })
    .catch(function (error) {
      console.log(error)
    })
}

function getCharacterMediaUrl_Old(rawData){
  let mediaUrl ={
    bust: rawData['bust_url'],
    avatar: rawData['avatar_url'],
    render: rawData['render_url']
  }
  return mediaUrl
}

function getCharacterMediaUrl_New(rawData){
  let mediaUrl ={}
  rawData['assets'].forEach(img=>{
    mediaUrl[img['key']] = img['value']
  })
  return mediaUrl
}

 

이후 nameplate 컴포넌트를 수정해준다

<template>
  <v-layout
    row
  >
    <v-flex
      class="px-6"
      xs12>
      <v-card>
        <v-layout>
          <v-img
            xs2
            min-height="150px"
            min-width="150px"
            max-width="180px"
            max-height="180px"
            :src=imgSource
          ></v-img>
          <v-flex xs12>
            <v-card-title primary-title>
              <v-col
                xs="12"
                md="5"
                lg="2"
                style="min-width: 400px;"
              >
                <h5>{{this.$store.state.characterProfile['title']}}</h5>
                <h1 :style="characterNameStyle">
                  {{this.$store.state.characterName}}
                </h1>
                <!--<h1 style="color: #d59012">color test</h1>-->
              </v-col>
              <v-col xs="12" md="6">
                <v-row>
                  <h3 v-if="this.$store.state.characterProfile['level']">
                    {{this.$store.state.characterProfile['equipped_item_level']}} LV
                  </h3>
                </v-row>
                <v-row>
                  {{this.$store.state.characterProfile['level']}}
                  {{this.$store.state.characterProfile['race']}}&nbsp
                  <span :style="characterNameStyle">{{this.$store.state.characterProfile['active_spec']}}</span>&nbsp
                  <span :style="characterNameStyle">{{this.$store.state.characterProfile['character_class']}}</span>
                </v-row>
                <v-row>
                  <span style="color: #ffa500;" v-if="this.$store.state.characterProfile['guild']">
                    <{{this.$store.state.characterProfile['guild']}}>
                  </span>
                  &nbsp
                  {{this.$store.state.characterProfile['realm']}}
                </v-row>
              </v-col>
              <v-col>

              </v-col>
            </v-card-title>
          </v-flex>
        </v-layout>
      </v-card>
    </v-flex>


  </v-layout>
</template>

<script>
  export default {
    name: "NamePlate",
    data(){
      return{
      }
    },
    computed: {
      imgSource:function(){
        return this.$store.state.characterMedia['avatar']
      },
      characterNameStyle: function () {
        let red = 255
        let green = 255
        let blue = 255
        let alpha = 255
        let characterClass = this.$store.state.characterProfile['character_class']

        let characterRGBA = {
          none: [255, 255, 255, 255], // RGBA
          Warrior: [175, 145, 100, 255],
          Mage: [140, 200, 255, 255],
          'Death Knight': [205, 0, 0, 255],
          Hunter: [130, 210, 90, 255],
          Priest: [255, 255, 255, 255],
          'Demon Hunter': [130, 60, 120, 255],
          Monk: [70, 210, 150, 255],
          Paladin: [255, 170, 220, 255],
          Rogue: [240, 255, 150, 255],
          Shaman: [40, 60, 255, 255],
          Warlock: [100, 70, 200, 255],
          Druid: [210, 145, 20, 255]
        }
        if (characterRGBA.hasOwnProperty(characterClass)) {
          return {
            color: `rgba(
            ${characterRGBA[characterClass][0]},
            ${characterRGBA[characterClass][1]},
            ${characterRGBA[characterClass][2]},
            ${characterRGBA[characterClass][3]})`
          }
        } else {
          return {
            color: `rgba(
            ${characterRGBA['none'][0]},
            ${characterRGBA['none'][1]},
            ${characterRGBA['none'][2]},
            ${characterRGBA['none'][3]})`
          }
        }
      }
    }
  }
</script>

<style scoped>

</style>

 

이미지 소스가 실시간으로 변경되면 적용될 수 있도록 computed에 imgSource를 넣어주고 img태그의 src에 바인딩해준다 

처음엔 모르고 data부분에 넣어주었는데, 이렇게 하면 다른 캐릭터를 검색할 때에 이미지가 변하지 않더라. 

원래 이번 포스팅에 auto나 waterfall을 이용해 콜백 교통정리를 해줄 생각이었는데... 검색한대로 시도해보아도 잘 되지 않아 여기서 포스팅을 끊어 가겠다

 

지난번에 api서버와의 통신으로 토큰을 가져오는데에 성공했다.

이번엔 그 토큰으로 내가 필요한 데이터를 받아올것이다.

그 전에, 작업을 하면서 시각적으로 변화를 확인할 수 있도록 네임플레이트를 만들어보겠다.

<template>
  <v-layout row>
    <v-flex
      class="px-6"
      xs12>
      <v-card>
        <v-layout>
          <v-img
            xs2
            min-height="150px"
            min-width="150px"
            max-width="180px"
            max-height="180px"
            src="https://render-kr.worldofwarcraft.com/character/azshara/0/119314432-avatar.jpg"
          ></v-img>
          <v-flex xs12>
            <v-card-title primary-title>
              <v-col
                xs="12"
                md="5"
                lg="2"
                style="min-width: 400px;"
              >
                <h5>TITLE</h5>
                <h1>
                  캐릭터 닉네임
                </h1>
                <!--<h1 style="color: #d59012">color test</h1>-->
              </v-col>
              <v-col xs="12" md="6">
                <v-row>
                  <h3>
                    100LV
                  </h3>
                </v-row>
                <v-row>
                  60레벨
                  종족&nbsp
                  <span>특성</span>&nbsp
                  <span>직업</span>
                </v-row>
                <v-row>
                  <span style="color: #ffa500;">
                      길드명
                  </span>
                  &nbsp
                  서버
                </v-row>
              </v-col>
              <v-col>

              </v-col>
            </v-card-title>
          </v-flex>
        </v-layout>
      </v-card>
    </v-flex>


  </v-layout>
</template>

<script>
  export default {
    name: "NamePlate"
  }
</script>

<style scoped>

</style>
//네임플레이트

이제 이 네임플레이트에 사용자가 캐릭터 이름을 검색하면 해당 캐릭터의 정보가 출력되도록 해볼것이다.

이에 필요한 검색창을 하나 만들것이다.

한 캐릭터를 특정하기 위해서는 두가지 정보 - 서버와 캐릭터명이 필요하다.

현재 게임에 존재하는 서버의 리스트를 서버에서 가져온다.

export function getRealmSlug(store){
  this.$axios.$get('https://kr.api.blizzard.com/data/wow/realm/index',{
    params:{
      region:'kr',
      namespace:'dynamic-kr',
      locale:'ko-KR',
      access_token:store.state.accessToken
    }
  })
    .then(function (result) {
      store.commit('setRealmSlugList',getRealmSlugList(result))
    })
    .catch(function (error) {
      console.log(error)
    })
}

function getRealmSlugList(rawData){
  let data = []
  rawData.realms.forEach(realm => {
    let tmp = {
      slug: realm['slug'],
      ko_KR: realm['name']['ko_KR'],
      en_US: realm['name']['en_US']
    }
    data.push(tmp)
  })
  return data
}

//서버 리스트 통신
import secret from '../apiAccount'


export function getAccessToken(store) {
  let data = `client_id=${secret.clientID}&client_secret=${secret.clientSecret}&grant_type=client_credentials`
  return this.$axios.$post('https://us.battle.net/oauth/token', data)
    .then(function (response) {
      console.log(response)
      store.commit('setAccessToken', response.access_token)
      return true
    })
    .catch(function (error) {
      console.log(error)
      return false
    })
}
//토큰 통신
async mounted() {
    await this.$store.dispatch('getAccessToken')
    if(this.$store.state.accessToken !== 'default Token String'){
      this.$store.dispatch('getRealmSlug')
    }else{
        //
    }
  }
  //layout의 mounted hook
export default {
  setAccessToken(state, payload){
    state.accessToken = payload
  },
  setRealmSlugList(state,payload){
    state.realmSlugList = payload
  },
}
//mutation

서버 리스트를 요청하기 전에 반드시 getAccessToken 함수가 선행되어야하기 때문에 async/await를 사용해 토큰을 받아온 후에 getRealmSlug를 실행한다.

서버 목록이 정상적으로 state에 저장된것을 확인할 수 있다.

이 다음으로는 네임플레이트에 들어갈 정보들을 불러온다.

export function getCharacterProfile(store){
  this.$axios.$get(`https://kr.api.blizzard.com/profile/wow/character/${store.state.realmSlug}/${store.state.characterName}`,{
    params:{
      region:'kr',
      namespace:'profile-kr',
      locale:'en_US',
      access_token: store.state.accessToken
    }
  })
    .then(function (response) {
      store.commit('setCharacterProfile',getProfileData(response))
    })
    .catch(function (error) {
      console.log(error)
    })
}

function getProfileData(rawData){
  let profile ={
    faction: rawData['faction']['type'],
    title:'',
    realm: rawData['realm']['name'],
    active_spec: rawData['active_spec']['name'],
    race: rawData['race']['name'],
    character_class: rawData['character_class']['name'],
    equipped_item_level: rawData['equipped_item_level'],
    guild:'',
    level: rawData['level'],

  }
  if(rawData.hasOwnProperty('active_title')){
    profile['title'] = rawData['active_title']['name']
  }
  if(rawData['guild'].hasOwnProperty('name')){
    profile['guild'] = rawData['guild']['name']
  }
  return profile
}
//캐릭터 정보

profile에 들어갈 정보중 타이틀, 길드 등 캐릭터가 가지고 있지 않을 수 있는 정보들은 예외처리를 해주어야한다. api를 불러올떄 해당 정보가 없으면 에러가 발생하기때문이다.

export function getCharacterData(store,payload){
  store.commit('setSearchInfo',payload)
  store.dispatch('getCharacterProfile')
}
export default {
  setAccessToken(state, payload) {
    state.accessToken = payload
  },
  setRealmSlugList(state, payload) {
    state.realmSlugList = payload
  },
  setCharacterProfile(state, payload) {
    state.characterProfile = payload
  },
  setSearchInfo(state,payload){
    state.characterName = payload['characterName']
    state.realmSlug = payload['realmSlug']
  }
}
//mutation

이제 내가 검색한 캐릭터에 대한 정보를 성공적으로 받아온것을 확인할 수 있다.

이제 만들어둔 네임플레이트에 state에 저장된 정보를 알맞게 넣어주면 된다.

<template>
  <v-layout
    row
  >
    <v-flex
      class="px-6"
      xs12>
      <v-card>
        <v-layout>
          <v-img
            xs2
            min-height="150px"
            min-width="150px"
            max-width="180px"
            max-height="180px"
            src="https://render-kr.worldofwarcraft.com/character/azshara/0/119314432-avatar.jpg"
          ></v-img>
          <v-flex xs12>
            <v-card-title primary-title>
              <v-col
                xs="12"
                md="5"
                lg="2"
                style="min-width: 400px;"
              >
                <h5>{{this.$store.state.characterProfile['title']}}</h5>
                <h1 :style="characterNameStyle">
                  {{this.$store.state.characterName}}
                </h1>
                <!--<h1 style="color: #d59012">color test</h1>-->
              </v-col>
              <v-col xs="12" md="6">
                <v-row>
                  <h3 v-if="this.$store.state.characterProfile['level']">
                    {{this.$store.state.characterProfile['equipped_item_level']}} LV
                  </h3>
                </v-row>
                <v-row>
                  {{this.$store.state.characterProfile['level']}}
                  {{this.$store.state.characterProfile['race']}}&nbsp
                  <span :style="characterNameStyle">{{this.$store.state.characterProfile['active_spec']}}</span>&nbsp
                  <span :style="characterNameStyle">{{this.$store.state.characterProfile['character_class']}}</span>
                </v-row>
                <v-row>
                  <span style="color: #ffa500;" v-if="this.$store.state.characterProfile['guild']">
                    <{{this.$store.state.characterProfile['guild']}}>
                  </span>
                  &nbsp
                  {{this.$store.state.characterProfile['realm']}}
                </v-row>
              </v-col>
              <v-col>

              </v-col>
            </v-card-title>
          </v-flex>
        </v-layout>
      </v-card>
    </v-flex>


  </v-layout>
</template>

<script>
  export default {
    name: "NamePlate",
    computed: {
      characterNameStyle: function () {
        let red = 255
        let green = 255
        let blue = 255
        let alpha = 255
        let characterClass = this.$store.state.characterProfile['character_class']

        let characterRGBA = {
          none: [255, 255, 255, 255], // RGBA
          Warrior: [175, 145, 100, 255],
          Mage: [140, 200, 255, 255],
          'Death Knight': [205, 0, 0, 255],
          Hunter: [130, 210, 90, 255],
          Priest: [255, 255, 255, 255],
          'Demon Hunter': [130, 60, 120, 255],
          Monk: [70, 210, 150, 255],
          Paladin: [255, 170, 220, 255],
          Rogue: [240, 255, 150, 255],
          Shaman: [40, 60, 255, 255],
          Warlock: [100, 70, 200, 255],
          Druid: [210, 145, 20, 255]
        }
        if (characterRGBA.hasOwnProperty(characterClass)) {
          return {
            color: `rgba(
            ${characterRGBA[characterClass][0]},
            ${characterRGBA[characterClass][1]},
            ${characterRGBA[characterClass][2]},
            ${characterRGBA[characterClass][3]})`
          }
        } else {
          return {
            color: `rgba(
            ${characterRGBA['none'][0]},
            ${characterRGBA['none'][1]},
            ${characterRGBA['none'][2]},
            ${characterRGBA['none'][3]})`
          }
        }
      }
    }
  }
</script>

<style scoped>

</style>

직업 구분을 위해 폰트에 직업에 맞는 색상을 지정하고 넣어주었다.

프로필 정보를 받아올때와 마찬가지로, 길드와 타이틀은 예외처리를 해주어야한다.

blizzard open api와 nuxt를 이용한 캐릭터 정보 검색 어플리케이션을 만들었다.

버전업과 코드 정리를 겸하며 github에 올리려고 한다.

일단 먼저 api와 통신하려면 토큰이 필요하기 때문에 가장 먼저 토큰을 받아 오는 기능을 만들어준다.

우선 store/action에 getAccessToken 함수를 만든다.

import secret from '../apiAccount'

export function getAccessToken(store) {
  let data = `client_id=${secret.clientID}&client_secret=${secret.clientSecret}&grant_type=client_credentials`
  this.$axios.$post('https://us.battle.net/oauth/token', data)
    .then(function (response) {
      store.commit('setAccessToken', response.access_token)
    })
    .catch(function (error) {
      console.log(error)
    })
}

아래는 setAccessToken 코드이다. mutation/index에 작성되었다.

export default {
  setAccessToken(state, payload){
    state.accessToken = payload
  }
}

아래는 store/state/index에 작성되었다.

export default {
  accessToken:''
}

위 코드는 블리자드 api와 통신해 토큰을 받아와 store/state에 저장하는 함수이다.

그런데 토큰을 받아오기 위해서는 내 블리자드 계정의 clientID와 clientSecret이 필요하다.

해당 키는 블리자드 api 사이트에서 발급받을 수 있다.

github에 업로드 해야하기 때문에 내 ID와 Secret은 올라가지 않게 해야한다.

아래 코드는 apiAccount이다.

export default {
  clientID: 'input your blizzard api client ID',
  clientSecret : 'input your blizzard api client secret'
}

이렇게 github에 업로드 해두고, 내가 작업할 때에는 내 ID와 secret이 들어가도록 수정한다.

그리고 내가 수정한 코드가 다시 github에 업로드 되지 않게 설정해둔다.

프로젝트의 파일 중 .gitignore 파일에 업로드 하고싶지 않은 파일을 넣어주면 된다.

store/action/index에서 앞으로 생성될 action들을 관리해줄것이다.

import {getAccessToken} from './getAccessToken'

export default {
  getAccessToken: getAccessToken
}

store/index에서 state/mutation/action을 관리한다

import myState from './state/index'
import myMutation from "./mutation/index"
import myActions from "./action/index"

export const state = () => (myState)
export const mutations = myMutation
export const actions = myActions

 

이제 api서버에서 토큰을 받아올 준비가 끝났다.

토큰을 불러오는 action을 dispatch해주기만 하면 된다.

<script>
  import routeInfo from './routeInfo'

  export default {
    data() {
      return {
        clipped: false,
        drawer: false,
        fixed: false,
        items: routeInfo,
        miniVariant: false,
        right: true,
        rightDrawer: false,
        title: 'Vuetify.js'
      }
    },
    mounted() {
      this.$store.dispatch('getAccessToken')
    }
  }
</script>

layouts/defalut 파일의 script에 mounted 훅을 걸어서 해당 페이지 레이아웃이 생성될 때 토큰을 받아오는 action이 실행되도록 한다.

정상적으로 토큰을 가져오는데에 성공했다.

+ Recent posts