-
첫번째 포켓몬, 에이스번 구현 (기습, 무릎차기)포켓몬 배틀 구현 2022. 3. 16. 18:16
첫번째로 구현한 포켓몬은 8세대의 스타팅중 하나, 에이스번이다.
8세대 첫 포켓몬이기도 하고,
소드실드를 못해본만큼 한번 꼭 써보고싶은 포켓몬이라 제일 먼저 이를 구현해봤다.
그리고 이제와서 말하는건데, 다른 포켓몬들보다 구현할게 배는 많았다(...)
뭐 그래도 아직 코드가 복잡하지 않은 초반에 이를 구현한게 오히려 다행인가 싶기도 하다.
일단 구현해야 하는것은
1.특성 리베로
2.아이템 기합의 띠
3.상태이상 화상
4.상태이상 독
5.기습
6.무릎차기
일단 처음 구현인만큼 상태이상도 구현해야하고,
특성이랑 아이템이랑
심지어 기습이랑 무릎차기도 따로 구현해야했다.
할거많아 신난다.
https://pikalytics.com/pokedex/homebss/cinderace
Cinderace Series 12 Battle Stadium Singles Stats Pokédex | Pokémon | Pikalytics
Life Orb 52.400% Focus Sash 25.000% Choice Scarf 13.200% Assault Vest 3.400% Lum Berry 2.000% Choice Band 1.100% Expert Belt 1.000% Scope Lens 0.700% Rocky Helmet 0.300% Liechi Berry 0.300%
pikalytics.com
기술배치와 아이템 등은 통계 사이트를 참조해
가장 많이 사용하는 기술들과 아이템을 골랐다.
사실 화염볼, 무릎차기, 기습은 각각 선택률 1,2,4위이다만
더스트슈트는 7위다.
어쩌다 이 스킬을 고르게됐냐면
3위는 bounce, "뛰어오르다"라는 스킬이다.
첫번째 턴에 뛰어올라, 두번째 턴에 공격하는 스킬로
사실 이게 구현하기 되게 어렵다.
구현하기 귀찮은 스킬들은 많았지만, 구현하기 어려운 스킬은 이게 거의 유일하다 싶다.
아무래도 이전 턴에 사용한 스킬이 다음 턴에 영향을 주는 기술이다보니...
그래도 어렵고 복잡해도 필요하다면 구현하겠다! 라는 마인드였다만
문제는 이거 불필요하다.
이런 스킬을 사용하는 이유는 "다이맥스"라는 배틀 시스템 떄문으로
쉽게 말하면 3턴동안 포켓몬이 겁나 쎄진다.
이렇게 포켓몬이 거대해지고
체력이 2배, 스킬 위력이 대폭 상승하는 시스템인데,
이게 3턴동안 유지되다보니 다이맥스의 턴을 끌기위해 채용하는게 뛰어오르다와 같은 스킬이다.
상대가 다이맥스를 썼을때 뛰어오르다를 쓰면 공격을 맞지 않고,
이후 또 뛰어오르다를 쓰면
다이맥스 3번의 공격중 2방을 피할수있다.
문제는 다이맥스 시스템은 아직 구현 안했고 구현할 생각도 없어
하더라도 마지막에 하려고 미뤄둔 AI 개발까지 끝낸 다음에
심심하면 추가할텐데,
이거떄문에 뛰어오르다를 굳이 힘들게 구현할 필요는 없을것같았다.
이는 맨 마지막에 추가한다면 추가하도록 하고.
선택률 5위 기가임팩트
이것도 다이맥스떄문에 채용하는 스킬이다.
정확히는 뛰어오르다는 상대방의 다맥 턴을 뺄려고 채용하는 스킬이고
기가임팩트는 내가 다이맥스를 했을때 사용하려고 쓰는 스킬이다.
뭐 쩄든 이것도 같은 이유로 패스.
6위는 플레어드라이브는 화염볼을 채용했으니 패스.
그렇게 7위의 더스트슈트를 채용하였다.
그렇게 구현해야할 스킬들은 다 정했고,
드디어 에이스번 구현 시작이다.
일단 화상부터 구현해보기로 했다.
이러한 부가효과는 "부가효과" 함수를 통해 공격한 다음
이벤트가 발생하도록 하였다.
https://khs20010327.tistory.com/21
"부가효과" 함수의 구현
void 부가효과(string a[][one][two], int skillnumber, string b[][one][two], int aa, int bb, int s, int alife[], int blife[], int ps[], int bt[], int damage) { 배틀 파트의 마지막 함수, "부가효과" 함..
khs20010327.tistory.com
자세한것은 이 부가효과 글 참고
생각해보니 화상 구현은 부가효과 글에서 이미 다 썼네,
포켓몬의 화상 여부는 각 포켓몬의 정보 배열 [5][1]칸에 저장되어
해당 값이 "y"면 해당 포켓몬은 화상 상태이다.
"부가효과" 함수에서 화상을 걸고, 공격력이 깎이는 것을 구현했고
"후처리" 함수에서 화상으로 인한 데미지를 구현해놨다.
화상에 관해서는 설명할게 없으니,
이에 관한 이야기를 하나 풀어보자.
https://khs20010327.tistory.com/16
처음에 기획을 잘해야 하는 이유
보이나 이 아름다운 5000줄짜리 코드가(...) 앞서 말했듯이 포켓몬은 2차원 배열로 구현했다. 그리고 플레이어와 cpu의 포켓몬들은 2차원 배열 3개로 구성된 3차원 배열로 구성되었다. 다만 이는 한
khs20010327.tistory.com
이전 글에서 말했지만, 현재 코드는 한번 초벌로 쓴 다음 갈아엎고 새로 작성한 2번째 코드이다.
이때 "교체" 함수는 "기절" 함수로 작성하였고, 3차원 배열을 2차원 배열 3개로 구현했다가
비슷한 코드를 36번 복붙하고(...) 코드가 5000줄 가까이 되었다.
그래도 이게 아쉽지 않은 이유는, 처음에 코드를 작성하며 수정해야할 부분을 알 수 있었다.
현재 코드의 경우 선공 기술의 명중 여부를 확인하고, 데미지를 계산하여, 데미지를 준다.
그 다음 후공 기술의 명중 여부를 확인하고, 데미지를 계산하고, 데미지를 준다.
다만 첫 코드는 달랐는데
어차피 교체는 배틀 전에 완료되므로,
때리는 포켓몬과 맞는 포켓몬은 달리지지 않는다.
사용하는 기술도 이미 정해져있다.
즉 서로에게 주는 데미지를 사전에 먼저 계산해놓고,
선공의 명중 -> 데미지 계산
후공의 명중 -> 데미지 계산
이런식으로 코드를 짰었다.
그런데 여기서 문제가 생겼으니, 화상을 입으면 공격력이 절반으로 준다.
선공으로 상대방이 화상을 입으면, 상대방의 후공 데미지는 절반이 되어야한다.
그런데 데미지 계산은 이미 완료해버렸다.
뭐 이거는 상대방이 화상에 걸렸을때 상대방의 공격력을 절반으로 깎고,
현재 데미지 (이는 절반으로 깎인 공격력이 적용이 안된다. 이미 계산해놨으니까)를 절반으로 줄인다.
이런식으로 야매로 해결 가능했다.
다만 이런 문제가 여러번 발생했고
2번째 코드를 작성할때는 이를 미리 감안하여
선공 명중 -> 데미지 계산 -> 데미지 적용
이런식으로 데미지 계산을 미리하지 않고 배틀중에 하도록 작성하였다.
이 외에 교체, 스피드, 특성 3개가 연관되어 문제가 발생하여
이 역시 2번째 코드에서 수정하여 작성하였는데,
이는 다음에 이야기하도록 하고...
if (p[ps[0]][5][2] == "y" && plife[ps[0]] > 0) { cout << p[ps[0]][0][11] << s1(p, ps[0]) << "독 데미지를 입었다! ("; cout << plife[ps[0]] << "-" << stoi(p[ps[0]][0][3]) / 8 << "="; plife[ps[0]] -= stoi(p[ps[0]][0][3]) / 8; // 독 데미지는 최대 체력의 1/8 cout << plife[ps[0]] << ")\n"; if (plife[ps[0]] <= 0) { cout << p[ps[0]][0][11] << s1(p, ps[0]) << "쓰러졌다!\n"; }
"독" 상태이상은 "화상"과 매우 비슷하다.
걸리지 않는 타입을 불꽃에서 독, 강철로 바꾸고
공격력을 절반 깎는 기능을 없애고
텍스트만 좀 수정하면 바로 구현 완료!
여기도 심심하니 살짝 이야기를 덧붙이자면
"독"과 "맹독"은 다른 상태이상이다.
독은 매 턴 최대 체력의 1/16로 데미지가 고정인 반면
맹독은 1/16, 2/16, 3/16으로 턴이 지날수록 강해진다. 그리고 교체하면 초기화된다.
둘다 인게임에선 똑같이 "독"이라 써져있어 라이트 유저들은 잘 모른다.
*독 글자의 색깔이 살짝 달라서 구별 자체는 가능하다.
다음 구현할 것은 기술 무릎차기,
이는 실패하면 최대 체력 1/2의 데미지를 입는 기술로,
데미지 계산 전에 호출하는 "위력증감"이나
데미지 계산 후에 호출하는 "부가효과" 함수에서가 아닌,
실패한 경우에 "무릎차기실패" 함수를 따로 만들어서 구현해야한다.
이 무릎차기가 실패하는 경우는 3가지가 있다
첫번째는 상대방이 격투 무효인 고스트 상성인 경우
두번째는 상대방이 "방어"로 막은 경우
세번째는 그냥 빗나간 경우
3개의 경우에 "무릎차기 실패" 함수를 작성하여 호출하며 된다.
void 무릎차기실패(string p[][one][two], int k, int pn, int plife[], int ps[], string s[][one][two], int sn, int bt[]) { if (k == 0) { cout << "상대 "; } cout << p[pn][0][11] << s1(p, pn) << "의욕이 넘쳐서 땅에 부딪혔다! ("; int mdamage = 524; mdamage = stoi(p[pn][0][3]) / 2; //무릎차기 실패로 인한 데미진느 최대 체력의 절반 if (plife[pn] <= mdamage) mdamage = plife[pn]; //이 데미지가 현재 체력보다 높다면 현재 체력만큼만 데미지 cout << plife[pn] << "-" << mdamage << "="; plife[pn] -= mdamage; cout << plife[pn] << ")\n"; //최대 체력의 절반만큼 데미지를 입고, 데미지 계산 출력 if (plife[pn] <= 0) { if (k == 0) { cout << "상대 "; } cout << p[pn][0][11] << s1(p, pn) << "쓰러졌다!\n"; bt[1] = -1; } //이로 인해 쓰러졌을경우 bt 배열에 -1값을 리턴. } //무릎차기가 실패한 경우 텍스트와 데미지 계산을 처리하는 함수
구현은 간단하다.
최대 체력의 절반 값 stoi(p[pn][0][3]) / 2를 int 변수인 mdamage에 저장,
해당 값이 현재 체력보다 높다면 현재 체력만큼만 데미지를 준다.
현재 체력에 mdamage값 만큼 깎고 이를 텍스트로 출력한다.
이로인해 체력이 0 이하가 됐다면 "쓰러졌다"라는 텍스트를 추가로 출력하고 bt[1] 값을 -1로 바꾼다
이는 선공이 먼저 기절하였을경우 후공의 공격을 실패하게 하는 트리거이다.
이를 위에서 말한 3가지 경우의 수에서 호출한다.
이는 "명중" 함수에서 구현하였고, 해당 글에서 설명하였었다.
다음 구현할 대상은 기술 "기습"이다.
기습 고유의 성공 조건과 우선도도 구현해야 한다.
https://khs20010327.tistory.com/17
우선도와 스피드 구현
포켓몬은 "스피드"와 "우선도"라는 개념이 존재한다. 일단 스피드는 말 그대로 포켓몬의 스피드를 나타낸다. 각 포켓몬은 고유의 스피드 개체값을 지니고있고, 이를 노력치, 아이템 등으로 보정
khs20010327.tistory.com
우선도는 해당 글에서 설명한 "우선도비교" 함수에서 구현하였고,
이제 기습의 성공 조건을 알아보자면
1.내가 상대방보다 빠른 경우
2.상대가 공격기를 사용한 경우 이다.
이 두 조건은 speed와 attack 변수를 통해 쉽게 알 수 있다.
이는 둘중 누가 선공인지, 그리고 상대방이 공격을 하였는지 체크하기 위해 만든 변수로
기존의 변수 값만 전달되면 쉽게 구현 가능했다.
저것들은 함수 인자로 추가하는게 귀찮아서 그렇지....
그리고 살짝 생각해봤는데
기습을 성공하는 경우를 구현하는 것보다
기습을 실패한 경우를 구현하고 아닌 경우를 else로 처리하는게 맞는것같아
해당 방식으로 구현하였다.
if (p[pn][number][0] == "기습") {//사용한 스킬명이 기습일때 if (k == 1) {//k가 1일때 = 내가 썼을때 if (spd != 1 || sattck != 1) { gsp = -1; } } //내가 상대보다 느리거나 상대가 공격을 안했으면 실패 else if (k == 0)//k가 0일때 = 상대가 썼을때 if (spd != 0 || sattck != 1) { gsp = -1; } //내가 상대보다 빠르거나 내가 공격을 안했으면 실패 }
"명중" 함수에서 기습 카운터 gsp를 선언하고,
기습 성공 조건을 만족하지 않으면 gps 값을 -1로 한다.
if (k == 0) { cout << "상대 "; } cout << p[pn][0][11] << s1(p, pn) << p[pn][number][0] << t2(p, pn, number) << "사용했다!\n"; //스킬 사용 텍스트 if (gsp == -1 || bt[1] == -1 ) { //기습이 실패했거나, 공격할 대상이 이미 쓰러졌을 경우 printf("하지만 실패했다!\n"); }
이후 "ㅇㅇ은 ㅁㅁ스킬을 사용했다!" 텍스트 출력 이후
기습이나 bt 값에 따라 "하지만 실패했다!"를 출력한다.
둘을 붙여 둔 이유는
공격할 상대방이 이미 쓰러져 공격이 실패한 경우는 "무릎차기 실패"가 발동하지 않기에,
즉 둘이 목표하는 바가 같기때문에 붙여서 구현하였다.
기습은 사용 기술이 기습이므로 당연히 무릎차기 실패가 발동하지 않는다.
기술은 전부 구현 완료했고,
다음은 특성 "리베로"이다.
리베로는 자신이 사용한 기술과 같은 타입으로 변화하는 특성으로,
구현하기 어렵지는 않지만 귀찮았다.
정확히는 처음에 어떻게 구현해야할지 헷갈렸다.
일단 리베로가 발동하면, 사용한 스킬의 타입으로 변한다.
기습을 사용했다면 악타입으로, 더스트슈트를 사용했다면 독타입으로 변한다.
이렇게 타입이 변할때 고려해야할것은
자속보정과 방어상성 2가지이다.
그런데 자속 보정은 "위력증감" 함수에서 구현해놨기에,
그냥 에이스번의 타입, p[pp][0][1]값을 "불꽃"에서 "독"이나 "악"과 같이 필요한 타입으로 바꾸면 됐다.
그 다음은 방어상성.
방어상성은 해당 포켓몬의 정보 배열에 저장되어있으므로
해당 값들을 초기화하고 타입에 맞춰 값을 바꾸면 됐다.
void 리베로(string skillname, string p[][one][two], int pp, int s) { printf("[특성 리베로]\n"); for (int i = 6; i < 11; i++) { for (int j = 0; j < two - 1; j++) { p[pp][i][j] = 'null'; } }//에이스번의 방어 상성값을 초기화
일단 특성 리베로는 따로 함수로 구현하였고,
[특성 리베로]라는 텍스트를 출력하며
포켓몬의 방어상성을 담은 p[pp][6][]부터 p[pp][10][] 배열의 값을 전부 초기화한다.
if (skillname == "화염볼") {//특정 스킬을 사용한 경우 printf("에이스번은 불타입이 됐다!\n"); p[pp][0][1] = "불꽃"; //해당 스킬의 타입으로 변경 p[pp][7][0] = "땅";//또한 해당 타입의 방어 상성으로 변경 p[pp][7][1] = "물"; p[pp][7][2] = "바위"; p[pp][8][0] = "강철"; p[pp][8][1] = "벌레"; p[pp][8][2] = "불꽃"; p[pp][8][3] = "얼음"; p[pp][8][4] = "페어리"; p[pp][8][5] = "풀"; } else if (skillname == "무릎차기") { printf("에이스번은 격투타입이 됐다!\n"); p[pp][0][1] = "격투"; p[pp][7][0] = "비행"; p[pp][7][1] = "에스퍼"; p[pp][7][2] = "페어리"; p[pp][8][0] = "바위"; p[pp][8][1] = "벌레"; p[pp][8][2] = "악"; }
이후 해당 값들을 추가하면 된다.
사용한 기술 이름을 if문으로 확인하여,
p[pp][0][1] 타입을 바꾸고,
p[pp][7][] 2배 약점 타입들과
p[pp][8][] 0.5배 반감 타입들을 추가해줬다.
if (k == 0) { cout << "상대 "; } cout << p[pn][0][11] << s1(p, pn) << p[pn][number][0] << t2(p, pn, number) << "사용했다!\n"; //스킬 사용 텍스트 if (p[pn][0][12] == "리베로") { 리베로(p[pn][number][0], p, pn, k); }//에이스번일 경우 전용 텍스트 출력 if (gsp == -1 || bt[1] == -1 ) { //기습이 실패했거나, 공격할 대상이 이미 쓰러졌을 경우 printf("하지만 실패했다!\n"); }
해당 함수는 위의
"상대 ㅇㅇ는 ㅁㅁ 기술을 사용했다!"라는 텍스 툴력과
기습과 반격 실패 사이에서 호출한다.
마지막으로 구현할 것은 기합의 띠 하나!
기합의 띠는 풀피 상태에서 기절할 데미지를 입으면 1의 체력으로 버틴다.
여기서 주의깊게 봐야할것은 "풀피 상태에서"라는 조건인데
데미지를 입는 경우는 공격 당할때 말고도
상태이상 데미지, 반동 데미지, 무릎차기 실패 등 정말 경우가 많다.
다만 이런 데미지는 전부 체력 비례데미지고, 절때로 풀피 상태에서 해당 데미지로 한번에 죽지 않는다!
즉 상대방에게 공격을 받고 체력이 풀피에서 0 이하가 됐을떄만 고려하면 된다!
if (blife[bb] - damage < 0) damage = blife[bb]; //데미지가 현재 체력 이상이면 현재 체력 만큼 데미지 if (blife[bb] - damage <= 0 && b[bb][0][13] == "기띠" && beforelife == stoi(b[bb][0][3])) damage -= 1; //상대방 기띠 발동시 데미지를 1 줄임 if (b[bb][0][13] == "기띠" && beforelife == stoi(b[bb][0][3]) && blife[bb]==1) { cout << "[아이템 기합의띠]\n"; //지닌 아이템이 기합의 띠 이고 공격을 맞기 전 체력이 풀피였을 경우 b[bb][0][13] == "null"; //기합의 띠는 1회용이므로 발동되면 사라진다. if (s == 1) cout << "상대 "; cout << b[bb][0][11] << s1(b, bb); cout << "기합의띠로 버텼다!\n"; }
이는 "공격스킬" 함수 안에서 구현,
상대방에게 줄 데미지를 계산하고, 상대방의 현재 체력에서 해당 값 만큼 깎는다
이때 해당 데미지가 상대방 체력보다 높으면 체력 만큼만 데미지를 주고,
이때 맞는쪽의 아이템이 "기띠"이고
공격하기 전 체력값을 저장해둔 beforelife int 변수값이 최대 체력값과 동일하면 (= 풀피 상태에서 공격당함)
데미지를 1 깎는다. 즉 체력이 1 남는다.
똑같은 조건에 체력이 1일때
"기합의 띠로 버텼다" 텍스트를 출력한다.
위는 데미지 적용을 하기 전의 코드이고
아래는 데미지 적용 후의 코드이다
둘 사이에 "상대는 ~의 데미지를 입었다!" 텍스트를 출력해야 하기에
살짝 띄어서 구현되었다.
아무 생각없이 첫 포켓몬은 에이스번으로 구현했는데,
상태이상에 우선도에 전용 특성에 무릎차기에 정말 구현할게 많았다.
그래도 막상 첫 포켓몬 구현을 끝내고 나니 뿌듯했다.
구현 완료한 모습.
'포켓몬 배틀 구현' 카테고리의 다른 글
세번째 포켓몬, 썬더 구현 (변화기, 생구) (0) 2022.03.16 두번째 포켓몬, 자시안 구현 (랭크 변화) (0) 2022.03.16 "교체" 함수 구현 (0) 2022.03.16 "부가효과" 함수의 구현 (0) 2022.03.16 "공격 스킬" 함수의 구현 (0) 2022.03.16