whatisthis?

project. (js) 자동 텍스트 RPG 게임 - (수정) 본문

PRACTICE/SELF

project. (js) 자동 텍스트 RPG 게임 - (수정)

thisisyjin 2022. 1. 29. 15:04
 

javaScript. 자동 텍스트 RPG 게임

자동 텍스트 RPG 게임 index.html <!DOCTYPE html> 텍스트 RPG style.css * { margin: 0; box-sizing : border-box; } html { font-family:Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif; font-s..

mywebproject.tistory.com

지난시간 제작해보았던 자동 텍스트 RPG 게임의 기능을 추가하고,

더 개선된 버전을 제작해보았다.

 

이전 코드는 아래 [더보기] 를 참고 🔻

더보기

index.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>텍스트 RPG</title>
    <link rel="stylesheet" href="style.css" />
</head>
<body>
    <div id="log"></div>
    <script src="app.js"></script>
</body>
</html>

 

 

style.css

* {
    margin: 0;
    box-sizing : border-box;
}

html {
    font-family:Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif;
    font-size: 16px;
    line-height: 2;
    color: #1f2d3d;
}

body {
    text-align: center;
    width: 100%;
    height: 100vh;
    background-color: #f8f9fb;
}

body::after {
    content: "text-RPG";
    display: block;
    margin-top: 50px;
    color: #463bc1;
    font-size: 12px;
    font-weight: 600;
}

 

 

app.js

// 메세지를 #log에 추가

function logMessage(msg, color) {        
    if(!color) {color = 'black';}
    const div = document.createElement('div');
    div.innerHTML = msg;
    div.style.color = color;
    document.getElementById('log').appendChild(div);
}


// 캐릭터 생성자

let gameover = false;
let battle = false;

function Character(name, hp, att) {
    this.name = name;
    this.hp = hp;
    this.att = att;
}



// 메서드 ㅡ attacked / attack

Character.prototype.attacked = function(damage) {
    this.hp -= damage;
    logMessage(this.name + ' 의 체력이 ' + this.hp + ' 가 되었습니다.');
    if(this.hp <= 0) {
        battle = false;
    }
};

Character.prototype.attack = function(target) {
    logMessage(this.name + '이 ' + target.name + ' 을 공격합니다.');
    target.attacked(this.att);
};



// 영웅 생성자 ㅡ Character 상속

function Hero(name, hp, att, lev, xp) {
    Character.apply(this, arguments);
    this.lev = lev || 1;    //(lev = 1)
    this.xp = xp || 0;      //(xp = xp)
}

Hero.prototype = Object.create(Character.prototype);
Hero.prototype.constructor = Hero;



// 메서드 ㅡ attacked / attack / gainXp(경험치)

Hero.prototype.attacked = function(damage) {
    this.hp -= damage;
    logMessage(this.name + ' 님의 체력이 ' + this.hp + ' 남았습니다.');
    if(this.hp <= 0){
        logMessage('죽었습니다. 레벨' + this.lev + '에서 모험이 끝납니다. F5를 눌러 리스폰하세요.', 'red');
        battle = false;
        gameover = true;
    }
};

Hero.prototype.attack = function(target) {
    logMessage(this.name + ' 님이 ' + target.name + '을 공격합니다.');
    target.attacked(this.att);
    if(target.hp <= 0) {
        this.gainXp(target);
    }
};


Hero.prototype.gainXp = function(target) {
    logMessage('전투에서 승리하여 ' + target.xp + '의 경험치를 얻습니다.', 'blue');
    this.xp += target.xp;
    if(this.xp > 100 + 10 * this.lev) {
        this.lev++;
        logMessage('레벨 업! ' + this.lev + '레벨이 되었습니다.', 'blue');
        this.hp = 100 + this.lev * 10;
        this.xp -= 10 * this.lev + 100;          
    }
};


// 몬스터 생성자 ㅡ Character 상속

function Monster(name, hp, att, lev, xp) {
    Character.apply(this, arguments);
    this.lev = lev || 1;
    this.xp = xp || 10;
}

Monster.prototype = Object.create(Character.prototype);
Monster.prototype.constructor = Monster;


// 몬스터 랜덤으로 만드는 함수

function makeMonster() {
    const monsterArr = [
        ['rabbit', 25, 3, 1, 35],
        ['skeleton', 50, 6, 2, 50],
        ['soldier', 80, 4, 3, 75],
        ['king', 120, 9, 4, 110],
        ['devil', 500, 12, 6, 250]
    ];
    const monster = monsterArr[Math.floor(Math.random() * 5)];
    return new Monster(monster[0], monster[1], monster[2], monster[3], monster[4]);
}



///// 게임 진행 ㅡ ( 전투 > 승리 > 경험치업 > 전투 > 승리 > 레벨업 > ... )


const hero = new Hero(prompt('이름을 입력하세요.'), 100, 10);
logMessage(hero.name + ' 님의 모험이 시작됩니다.');

while(!gameover) {
    const monster = makeMonster();
    logMessage(monster.name + '을 마주쳤습니다. 전투가 시작됩니다.', 'green');
    battle = true;
    while(battle) {
        hero.attack(monster);
        if(monster.hp > 0) {
            monster.attack(hero);
        }
    }
}

 

 

 

 

 

 

index.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>텍스트 RPG</title>
    <link rel="stylesheet" href="style.css">
    
</head>
<body>

    
    <h1>auto-text RPG</h1>
    
    <div id="user-container">
        <div id="user-name">
            <h2>Your Name</h2>
            <form action="#" id="name-form">
                <input required type="text" maxlength="4" placeholder="name" />
                <button type="submit">OK</button>
            </form>
        </div>
    
    
        <div id="select-character">
            <h2>Choose Character!</h2>
            <input type="button" id="select-wizard">
            <input type="button" id="select-tanker">
        </div>
    </div>
 
    <div id="log">
    </div>

    <script src="app.js"></script>
</body>
</html>

 

- 우선, h1과 h2를 추가하였고

- div 컨테이너를 세분화해서 나눴다. (스타일 적용을 위해)

 

 

style.css 

* {
    margin: 0;
    box-sizing : border-box;
}

html {
    font-family:Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif;
    font-size: 16px;
    line-height: 2;
    color: #1f2d3d;
}

body {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    width: 100%;
    height: 100vh;
    background-color: #d2d8e4;
}

#log::after {
    content: "@thisisyjin";
    display: block;
    margin-top: 50px;
    color: #463bc1;
    font-size: 12px;
    font-weight: 600;
}


#user-container {
    display: flex;
    align-items: center;
}

#user-name {
    margin: 30px;
    text-align: center;
}

#name-form input, #name-form button {
    border: 1px solid #d2d8e4;
    height: 30px;
}

#name-form {
    padding-top: 15px;
    padding-bottom: 15px;
}



#select-wizard {
    width: 60px;
    height: 60px;
    border: 0;
    background: url( "./source/wizard.png" );
    cursor:pointer;
    background-size: cover;
    margin-left: 10px;
    margin-right: 30px;
}

#select-tanker {
    width: 60px;
    height: 60px;
    border: 0;
    background: url( "./source/tanker.png" );
    cursor:pointer;
    background-size: cover;
}


#log {
    text-align: center;
    height: 1vh;
}

 

 

중구난방이지만 ..

나중에 다듬어서 깔끔한 UI로 변경할 예정.

CSS 는 너무 어렵다!

 

app.js

// 메세지를 #log에 추가

function logMessage(msg, color) {        
    if(!color) {color = '#2f2f2f';}
    const div = document.createElement('div');
    div.innerHTML = msg;
    div.style.color = color;
    document.getElementById('log').appendChild(div);
}


// 캐릭터 생성자

let gameover = false;
let battle = false;

function Character(name, hp, att) {
    this.name = name;
    this.hp = hp;
    this.att = att;
}


// 메서드 ㅡ attacked / attack

Character.prototype.attacked = function(damage) {
    this.hp -= damage;
    logMessage(this.name + ' 의 체력이 ' + this.hp + ' 가 되었습니다.');
    if(this.hp <= 0) {
        battle = false;
    }  else {
        logMessage(this.name + ' 님의 체력이 ' + this.hp + ' 남았습니다.');
    }
};

Character.prototype.attack = function(target) {
    logMessage(this.name + '이 ' + target.name + ' 을 공격합니다.');
    target.attacked(this.att);
};



// 영웅 생성자 ㅡ Character 상속

function Hero(name, hp, att, lev, xp) {
    Character.apply(this, arguments);
    this.lev = lev || 1;    //(lev = 1)
    this.xp = xp || 0;      //(xp = xp)
}

Hero.prototype = Object.create(Character.prototype);
Hero.prototype.constructor = Hero;



// 메서드 ㅡ attacked / attack / gainXp(경험치)

Hero.prototype.attacked = function(damage) {
    this.hp -= damage;
    if(this.hp <= 0){
        logMessage('죽었습니다. 레벨 ' + this.lev + '에서 모험이 끝납니다.  F5를 눌러 리스폰하세요.', 'red');
        battle = false;
        gameover = true;
    } else {
        logMessage(this.name + ' 님의 체력이 ' + this.hp + ' 남았습니다.');
    }
};

Hero.prototype.attack = function(target) {
    logMessage(this.name + ' 님이 ' + target.name + '을 공격합니다.');
    target.attacked(this.att);
    if(target.hp <= 0) {
        this.gainXp(target);
    }
};


Hero.prototype.gainXp = function(target) {
    logMessage('🏆 전투에서 승리하여 ' + target.xp + '의 경험치를 얻습니다.', 'blue');
    this.xp += target.xp;
    if(this.xp > 100 + 10 * this.lev) {
        this.lev++;
        logMessage('🔺 레벨 업! ' + this.lev + '레벨이 되었습니다.', 'blue');
        this.hp = 100 + this.lev * 10;
        this.xp -= 10 * this.lev + 100;          
    }
};


// 몬스터 생성자 ㅡ Character 상속

function Monster(name, hp, att, lev, xp) {
    Character.apply(this, arguments);
    this.lev = lev || 1;
    this.xp = xp || 10;
}

Monster.prototype = Object.create(Character.prototype);
Monster.prototype.constructor = Monster;


// 몬스터 랜덤으로 만드는 함수

function makeMonster() {
    const monsterArr = [
        ['rabbit', 25, 3, 1, 35],
        ['skeleton', 50, 6, 2, 50],
        ['soldier', 80, 4, 3, 75],
        ['king', 120, 9, 4, 110],
        ['devil', 500, 12, 6, 250]
    ];
    const monster = monsterArr[Math.floor(Math.random() * 5)];
    return new Monster(monster[0], monster[1], monster[2], monster[3], monster[4]);
}




///// 게임 진행 ㅡ ( 전투 > 승리 > 경험치업 > 전투 > 승리 > 레벨업 > ... )

// 이름 ㅡ form과 input(text)
const nameForm = document.getElementById('name-form');
const nameInput = document.querySelector('#name-form input');

let savedName = '';
let charcterType = '';


// 캐릭터선택 ㅡ input(button)
const wizardButton = document.getElementById('select-wizard');  
const tankerButton = document.getElementById('select-tanker');


function typeWizard() {
    const hero = new Hero(savedName, 100, 15);
    logMessage(hero.name + ' (Wizard)님의 모험이 시작됩니다.');

    while(!gameover) {
        const monster = makeMonster();
        logMessage('❕ ' + monster.name + '을 마주쳤습니다. 전투가 시작됩니다.', 'green');
        battle = true;
        while(battle) {
            hero.attack(monster);
            if(monster.hp > 0) {
                monster.attack(hero);
            }
        }
    }


}

function typeTanker() {
    const hero = new Hero(savedName, 150, 10);
    logMessage(hero.name + ' (Tanker)님의 모험이 시작됩니다.');

    while(!gameover) {
        const monster = makeMonster();
        logMessage('❕ ' + monster.name + '을 마주쳤습니다. 전투가 시작됩니다.', 'green');
        battle = true;
        while(battle) {
            hero.attack(monster);
            if(monster.hp > 0) {
                monster.attack(hero);
            }
        }
    }


}


// 이름을 저장함 ㅡ 캐릭터 선택
nameForm.addEventListener('submit', nameSave);

function nameSave() {
    savedName = nameInput.value;

    wizardButton.addEventListener('click', typeWizard);
    tankerButton.addEventListener('click', typeTanker);
}

 

- 우선 생성자 부분과  logMessage부분은 동일하다.

- 변경된 것은, addEventListener부분 위주.

 

- 나는 addEventListener을 중첩해서 사용해보았다.

이게 효율적인지는 모르겠다.

나중에는 form에 제출된 값을 기준으로 다르게 작동하도록 변경할것임.

 

 

 

 

1. 폼이 제출 (form submit)

- 이름을 입력하고, 제출을 했을 때

(input태그에 required 속성을 적어놨으므로 반드시 뭐라도 적어서 submit 해야함)

 

> 2.  nameSave 함수 실행

- 함수밖에서 변수로 미리 선언해둔 savedName 변수에

input태그(element)의 value를 대입한다.

 

그리고나서 addEventListener을 기다린다.

>> 3. wizard 클릭시 ㅡ typeWizard 함수 실행

>>>> 4. hero = new hero(savedName, 100, 15)로 캐릭터 생성 후 플레이함. 

 

>> 3. tanker 클릭시  ㅡ typeTanker 함수 실행

>>>> 4. hero = new hero(savedName, 150, 10)로 캐릭터 생성 후 플레이함. 

 

 

 

 


 

 

 

변경사항

 

1 / 우선, prompt로 입력받던 이름(name)을 다른 방법으로 입력받고

2 / 캐릭터 종류를 세분화하여 hp와 att(공격력)을 달리 하여 유저가 선택할 수 있도록 했다.

3 / 추가적으로, CSS를 개선하여 보기 좋게 웹사이트를 제작했다.

(물론, 이건 js 연습이니까 베타버전 정도라 생각)

 

 

 

 

<캐릭터 종류>

wizard hp: 100 / att : 15
tanker hp: 150 / att : 10

 

 

- 이름을 입력받는 것은 input (text)를 이용해서 입력하고, form이 submit 되면

이름이 저장되는 것으로 하였다.

 

 

- 캐릭터 선택은 처음엔 radio로 해볼까 고민했는데, 뭔가 CSS 적용이 힘들 것 같아서버튼에 이미지를 background로 입히는 것으로 결정했다.

 

>> 이 과정에서<button> <img/> </button>을 해야할지<input type="button" />을 우선 만들고나서  CSS를 입힐지 고민했다.

 

그러나, img 태그가 정보를 담고있는 느낌보다는로고나 아이콘 느낌이므로 CSS 처리를 택했다.

 

#select-wizard {
    width: 60px;
    height: 60px;
    border: 0;
    background: url( "./source/wizard.png" );
    cursor:pointer;
    background-size: cover;
}

#select-tanker {
    width: 60px;
    height: 60px;
    border: 0;
    background: url( "./source/tanker.png" );
    cursor:pointer;
    background-size: cover;
}

 


 

플레이 화면

 

 

 

 

초기 화면

 

yjin / 마법사(wizard) 선택시

 

연진 / 탱커(tanker) 선택시

 

 

 

 

게임 로그 🔻

 

(..용량 완전 크다)

 

 

 

 

 

 

GitHub - thisisyjin/jsPractice

Contribute to thisisyjin/jsPractice development by creating an account on GitHub.

github.com

 

 

- 깃헙에 새로 업로드 해두었다.

- 나중에 차차 수정 + 개선해가면서 완성할 예정!

 

 

 

 


 

 

사담 👻

- 이것은 생성자함수로 캐릭터를 생성하지만 / 나중에 클래스 배우고나서 클래스 버전으로 수정할거임.
(아마 vscode에서 생성자함수를 권장하지않는 걸 보니 이제 클래스를 더 쓰나보다.)

- 나중엔 제대로 몬스터들과의 밸런스를 맞춰서

진짜 rpg게임처럼 만들어보는 것이 최종 목표이다.

- 레벨별로 등장하는 몬스터 확률도 다르게 조정 (밸런스를 위해)

- 나중엔 사용자가 스킬을 골라서 직접 턴제싸움으로 고퀄리티(?) 웹 RPG를 만들어보고싶다.
캐릭터 종류도 더 업그레이드하고, 포켓몬처럼 타입을 추가해서 서로 스킬별로 상성도 있으면 재밌을 듯.
(우주로 가는 프로젝트 규모^^. 게임회사 하나 차릴듯)