체력바 연동

턴제제작 2019. 1. 31. 04:42

 

 엑스컴을 하다가 두 번째 선택된 자를 제거하고 일단 끝을 냈다. 문득 드는 생각이 콘텐츠를 엑스컴에 한 1/3 정도 그러니까 첫 번째 선택된 자를 제거할 때쯤 되면 엔딩을 보도록 해야 할 듯싶다. 약간 부족하다 싶을 정도로 하루 종일 하면 콘텐츠가 다 소모될 정도로 만들어야 될 듯싶다. 콘텐츠 분량을 엑스컴 수준으로 만들려면 몇 년을 만들어도 부족할 듯싶다. 

 지금 고민을 하고 있는 게 클래스를 두 개를 선택할 수 있고 그 두 개를 고를 수가 있어서 플레이어가 선택적으로 조합 쪽으로 갈지 아니면 엑스컴처럼 한 개의 클래스에 2개의 트리가 있고 랜덤 하게 다른 클래스의 스킬을 배정하는 쪽으로 가야 할지가 고민이 든다. 첫 번째는 타이탄 퀘스트나 펠실을 염두에 둔 거다. fell seal은 좀 생소할 수도 있는데 지금 현재 얼리 어세스 중인 턴제 인디 게임이다. 

 아직 얼리 어세스 단계이지만 강추다 가서 꼭 해보길 바란다. 약간 아쉬운 점이 있다면 프로필 화면도 약간 동화풍으로 했으면 일관성 측면에서 좋았을 텐데 너무 대항해 시대 때 네덜란드 화풍으로 그린 게 아닌가 싶은 거 정도다. 

 아이템을 일일히 다 만들려면 힘드니까 검도 레벨업을 한다는 시스템으로 가야 할 듯싶다. 안에 아두이노 같은 소형 컴퓨터가 들어있고 학습 기능이 있다고 하면 될 듯싶다. 

 아직 나오지 않은 게임 중에는 이게임을 기대하고 있다. 도트 그래픽 게임은 잘하지는 않는데 이 정도 좆 간지 도트라면 충분히 해볼 만한 듯싶다. 나도 나중에 크라우드 펀딩 모금을 해야 할 듯싶다. 일단 트레일러에 나올만한 분량이 나오는 단계까지 계발을 하는 게 다음 목표다. 

 여튼 UI는 나중에 차차 고민하도록 하고 일단 가서 때리는 것부터 만들어야겠다. 

 일단 아군이든 적군이든 간에 체력바를 위에 달아 줘야겠다. 일단 아군과 적군의 색을 달리 해줘야 하는데 이는 오브젝트의 태그를 확인해 보면 될 듯싶다. 위치는 업데이트를 통해서 관리를 해주면 될 듯싶다. 펠실의 경우에는 위쪽에 고정되게 모아 두었는데 이렇게 해두려면 적 캐릭터 프로필도 다 만들어 주어야 하는 번거로움이 있기 때문에 위에 뜨도록 하는 게 속 편할 듯싶다. 

 때리는건 너무 사실적으로 딱 맞아떨어지게 할 필요는 없을 듯싶다. 턴제 게임의 게임적 허용이라고 해야 하나 엑스컴도 코앞에서 때리는데 100%가 아니라는 이유로 90%인데 빗나가고 이런 게 늘 있으니 말이다. 멀리 있을 때야 빗나가는 게 보이지만 가까이에서는 빗맞은 각이 안 나오다 보니 총알을 수십 발을 맞고 있는데도 멀쩡하고 그러니 말이다. 

  물론 이번에도 여전히 어떻게 만들어야 할지 감이 잘 안 와서 시작을 못하고 있다. 물론 지금까지 과정을 알기에 이것도 끝내 만들어낼 거라는 알지를 통해 알고 있지만 말이다. 클래스를 어떤 식으로 만들어야 하나 업데이트가 좋을지 코 루틴이 좋을지 어떻게 설계를 해야 할지를 해보지 않아서 걱정이 들어서 선뜻 시작을 못하고 있는 거 같다. 물론 이 포스트 끝에는 매번 그랬듯 만들고 보니 별거 아녔네 하고 혼자 앉아서 자화자찬을 할 거니 말이다. 왠지 늘 같은 패턴인 거 같다.  앉아만 있는다고 생각이 나나는게 아니니까 좀 산책이나 다녀와야겠다. 

 밥을 먹고왔는데 밥이 맛있지가 않다. 맨날 맛있게 먹던 건데 말이다. 

 난 내가 창의적인 사람이어서 주어진 범위에서 벗어나지 않고 효율적으로 처리하는 능력을 높게 치는 공무원 시험에 떨어진다고 생각했었다. 근데 아닌 거 같다. 막막하네.

 일단 코드를 정리하고 소소한 버그들을 잡아내고 있다. 

 일단 옵스타클이 아니라 플레이너나 에너미에도 방어가 되는 문제가 있다. 이 문제부터 해결하고 와야겠다. 

    void MakeShield()
    {
        if (this.tileStyle == TileStyle.NonWalkable) return;
        if (this.tileStyle == TileStyle.Normal) return;

        foreach (var direction in GameUtility.directions)
        {
            if (Physics.Raycast (transform.position + shield.halfShieldVector, direction, checkRange, LayerMask.GetMask ("Obstacle")))
            {
                shield.transformDict [direction].position = this.transform.position;
                isShield = true;

                if (Physics.Raycast (transform.position + shield.perfectShieldVector, direction, checkRange, LayerMask.GetMask ("Obstacle")))
                {
                    shield.spriteRendererDict [direction].sprite = shield.perfectShield;
                }
                else
                {
                    shield.spriteRendererDict [direction].sprite = shield.halfShield;
                }
            }
        }
    }

 

 코드 자체는 문제가 없는데 어디서 문제가 생긴 걸까 싶다. 

 문제는 유니티 인스펙터 창에 있었다. 에너미가 옵스타클로 설정이 되어 있어서 문제가 있었다. 

 이걸 해결 했으니 다시 하려던 걸로 넘어가 보자. 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UIManager : MonoBehaviour {

    GameManager gameManager;
    public GameObject barObject;
    public Transform nonPostCanvas;

    void Awake()
    {
        gameManager = GetComponent<GameManager>();
    }

    void Start()
    {
        foreach (var player in gameManager.playerList)
        {

        }

        foreach (var enemy in gameManager.enemyList)
        {
            
        }
    }

    void Update()
    {
        
    }
}

 

 일단 간단한 틀을 만들어 봤다. 이제 캔버스 위에다가 오브젝트를 생성시키면 될 듯싶다. 

using System.Collections;

using System.Collections.Generic;
using UnityEngine;

public class UIManager : MonoBehaviour {

    GameManager gameManager;
    public GameObject barObject;
    public Transform nonPostCanvas;
    public Vector3 posCorrection = new Vector3 (0f, 0f, 0f);
    public List<BarNode> barList = new List<BarNode>();

    void Awake()
    {
        gameManager = GetComponent<GameManager>();
    }

    void Start()
    {
        foreach (var player in gameManager.playerList)
        {
            var newClone = Instantiate
            (
                barObject,
                Camera.main.WorldToScreenPoint (player.transform.position + posCorrection),
                Quaternion.identity, nonPostCanvas
            );
            var newBar = new BarNode ();
            newBar.baseObject = player.transform;
            newBar.barObject = newClone;
            barList.Add (newBar);
        }

        foreach (var enemy in gameManager.enemyList)
        {
            var newClone =Instantiate
            (
                barObject,
                Camera.main.WorldToScreenPoint (enemy.transform.position + posCorrection),
                Quaternion.identity, nonPostCanvas
                
            );
            var newBar = new BarNode ();
            newBar.baseObject = enemy.transform;
            newBar.barObject = newClone;
            barList.Add (newBar);
        }
    }

    void Update()
    {
        foreach (var node in barList)
        {
            node.barObject.transform.position = Camera.main.WorldToScreenPoint (node.baseObject.position + posCorrection);
        }
    }
}

public class BarNode
{
    public Transform baseObject;
    public GameObject barObject;
}

 

 존나 첨엔 어떻게 해야 할지 몰라서 두려운데 실상 이것저것 만들다 보면 무아지경에 빠져서 코딩을 하게 되는 듯하다. 나도 참 씹 엄살 쟁인 듯싶다. 카메라를 움직이고 화면을 돌려도 언제다 대응이 된다. 이게 한 번에 모든 걸 만들려고 하니까 머리가 뱅글뱅글 도는 거 같다. 만들어야 할 껐을 아주 작은 단위까지 잘게 잘게 쪼갠 다음에 하나하나 차곡차곡 순차적으로 만들어야 최종적으로 원하는 걸 만들 수 있는 듯싶다. 완성된 상태를 상상하고 거기서부터 역순으로 필요한 걸 찾아 들어가는 능력이 필요한 듯싶다. 

 다음 만들 거는 체력 수치를 만들고 체력바를 연동을 하는 거다. 

 잠깐 추석이라 고향집에 내려갔다가 왔다. 여동생에게 볼이 뭉쳤다는 이야기를 들었는데 볼뭉침이 두통의 원인일 수도 있다고 해서 틈틈이 마사지 중이다. 이게 볼을 만지기만 해도 아프다. 

 엑스컴 보니까 UI 오른쪽 상단에 X가 턴 종료 더라 이걸 내가 ->로 바꾸어 놨더라. 일단 ->로 하고 캐릭터 바꾸는 것은 똑같이 사람 얼굴과 세모로 하고 그다음으로 화면 돌리는 거 시계방향이랑 반시계 방향으로 만들면 될 듯싶다. 귀환을 어떻게 구현해야 할지 모르겠다. 이게 엑스컴은 스카이 레인저가 있어서 어느 때나 호출할 수가 있다. 다키스트 던전은 퇴각을 하면 일정 확률도 퇴각이 된다. 이게 캐릭터가 6명밖에 없어서 한 명이라도 죽으면 게임오버가 되면 끝나는 SRPG의 성격을 띠기 때문에 그냥 죽으면 해당 미션 재시작으로 해야 하나 모르겠다. 전투 외 시스템을 어떻게 해야 할지 모르겠다. 도시를 하나 만들어서 돌아다니게 할 것인지 아니면 엑스컴이나 인비지블 잉크처럼 할 것인지 아니면 히어로즈 마이트 앤 매직처럼 할 것인지 말이다. 이동이 생략된 모바일 게임처럼 하는 것도 하나의 방식이 될 수도 있을 듯싶다. 인비지블 잉크처럼 말이다. 

 근육이 줄고 그 자리에 살이 들어가서 처지고 있다. 예전엔 하루 한시간씩 댄스 학원에 가서 춤을 추었었는데 돈아 낀다고 안 추니 문제가 생긴 거 같다. 무조건 하루에 한 번 등산을 다녀와야겠다. 

 처음 만들땐 엄청 재미가 있었는데 이젠 재미는 온데간데없고 머리만 아프다. 왜 다들 중간에 포기하는지 알듯 싶기도 하다. 이 고비를 넘기면 다시 점점 재미가 있어진다고 하더라. 

 채력 연동을 해야 되는데 그전에 이동할 때도 UI바가 뜨는 문제를 해결하고 가야겠다. 공격이나 이동시에는 모션에만 집중할 수 있도록 UI의 경우에는 안 뜨게 할 생각이다. 

void StartIndicator()
{
player.playerActiveState = PlayerActiveState.Moving;
player.GetComponent<CapsuleCollider>().enabled = false;
player.activeOff.Invoke();
canvas.enabled = false;
nonPostConvas.enabled = false;
cameraController.useKeyboardInput = false;
cameraController.useScreenEdgeInput = false;
}

void EndIndicator()
{
player.playerActiveState = PlayerActiveState.NotAnything;
player.GetComponent<CapsuleCollider>().enabled = true;
canvas.enabled = true;
nonPostConvas.enabled = true;
cameraController.useKeyboardInput = true;
cameraController.useScreenEdgeInput = true;
board.ResetBoard();
}

 

 이렇게 해주면 된다. 체력바를 연동시켜야 하는데 어떻게 해야 할지 막막하다. 오브젝트 생성 시에 플레이어의 정보를 넘겨주면 되려나. 그러면 생성을 어디서 했었는지 일지를 보며 확인을 해야 될 듯싶다. 바로 위에 있네 UI매니저에서 관장을 하는가 보다. 체력 같은 정보를 base라는 클래스를 따로 만들어 놨는데 이걸 Player 클래스에 통합을 해야 할 듯싶다. 이동의 경우에는 워낙 내용이 많이 들어가니까 따로 분류를 했는데 굳이 나눌필요는 없을 듯싶다. 

    void Start()
    {
        foreach (var player in gameManager.playerList)
        {
            var newClone = Instantiate
            (
                barObject,
                Camera.main.WorldToScreenPoint (player.transform.position + posCorrection),
                Quaternion.identity, nonPostCanvas
            );
            var newBar = new BarNode ();
            newBar.baseTransform = player.transform;
            newBar.barObject = newClone;
            barList.Add (newBar);
            newClone.GetComponent<HPBar>().maxHealth = player.baseHP;
        }

        foreach (var enemy in gameManager.enemyList)
        {
            var newClone =Instantiate
            (
                barObject,
                Camera.main.WorldToScreenPoint (enemy.transform.position + posCorrection),
                Quaternion.identity, nonPostCanvas
                
            );
            var newBar = new BarNode ();
            newBar.baseTransform = enemy.transform;
            newBar.barObject = newClone;
            barList.Add (newBar);
            newClone.GetComponent<HPBar>().maxHealth = enemy.baseHP;
        }
    }

 

 오브젝트 생성시에 발동되게 하려면 Awake()가 아니라 Start()에 두어야 한다. 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class HPBar : MonoBehaviour {

    Image healthBar;
    public float maxHealth = 5.0f;
    public float currentHealth = 5.0f;
    Transform block;
    float thisWidth;
    float oneBlockPos;
    int neededBlock;
    public GameObject blockObject;

    void Start()
    {
        block = this.transform.Find ("Block");
        healthBar = this.transform.Find ("HP").GetComponent<Image>();
        currentHealth = maxHealth;
        healthBar.fillAmount = currentHealth / maxHealth;
        thisWidth = this.GetComponent<RectTransform>().rect.width;
        oneBlockPos = thisWidth / maxHealth;
        neededBlock = (int) maxHealth;
        MakeBlock();
    }

    void MakeBlock()
    {
        // print (oneBlockPos);
        for (int i = 1; i < maxHealth; i++)
        {
            var newBlock = Instantiate (blockObject, block.position, Quaternion.identity, block);
            newBlock.GetComponent<RectTransform>().localPosition = new Vector3 (oneBlockPos * i, 0f, 0f);
        }
    }
    
    void Update()
    {
        healthBar.fillAmount = currentHealth / maxHealth;
    }
}

 

 지금 UI매니저에서 비슷한데 플레이어인지 에너미인지 따라 차이가 있을 뿐 동일한 역할을 하는 코드가 중복되고 있다. 동적인 언어의 경우에는 매개변수를 알아서 적용을 하지만 C# 같은 정적 언어의 경우에는 매개변수의 클래스가 다르면 일반적인 방식으로 안된다. 여전에 C# 함수형 프로그래밍 공부할 때 해당 문법을 배운 거 같은데 이번이 적용을 해볼 타이밍인 듯싶다. 

 아이고 이거 예전 포스트를 지워버렸다. 이게 지금 GetComponent 하면서 < > 안에 집어넣는 것처럼 만들어야 하는 걸로 어렴풋하게 기억하고 있다. 

    void MakeHPBar (GetParticipant<Player> type)
    {
        var newClone =Instantiate
        (
            barObject,
            Camera.main.WorldToScreenPoint (type.transform.position + posCorrection),
            Quaternion.identity, nonPostCanvas
                
        );
        var newBar = new BarNode ();
        newBar.baseTransform = type.transform;
        newBar.barObject = newClone;
        barList.Add (newBar);
        newClone.GetComponent<HPBar>().maxHealth = type.baseHP;
    }

 

public class GetParticipant<Type> : MonoBehaviour {

}

 

 일단 이런식으로 만들어 봤는데 문제는 모노비 해이 비어에는 baseHP라는 게 정의가 안 돼있다는 거다. 이건 내가 사용자 정의로 만든 거라 인터페이스로 추가해 주어야 할 듯싶다. 인터페이스를 처음 사용해 본다. 이게 됐으면 좋겠다. 

public interface IParticipant {

    int baseHP { get; set; }
}

public class GetParticipant<Type> : MonoBehaviour, IParticipant {

    public int baseHP { get; set; }
}

 

 이런식으로 만들어 줬더니 baseHP에 빨간색으로 뜬 게 사라졌다.

    void MakeHPBar (GetParticipant paticipaint)
    {
        var newClone =Instantiate
        (
            barObject,
            Camera.main.WorldToScreenPoint (paticipaint.transform.position + posCorrection),
            Quaternion.identity, nonPostCanvas
                
        );
        var newBar = new BarNode ();
        newBar.baseTransform = paticipaint.transform;
        newBar.barObject = newClone;
        barList.Add (newBar);
        newClone.GetComponent<HPBar>().maxHealth = paticipaint.BaseHP;
    }
}

public interface IParticipant {

    int BaseHP { get; set; }
}

public class GetParticipant : MonoBehaviour, IParticipant {

    public int BaseHP { get; set; }
}

 

 구지 제너릭으로 만들 필요가 없을 거 같아서 수정해 줬다. 

public class Enemy : MonoBehaviour, IParticipant {

    int baseHP = 10;

    public int BaseHP {get {return baseHP;} }
 

 

public class Player : MonoBehaviour, IParticipant {
    
    int baseHP = 10;
    public int BaseHP {get {return baseHP;} }

 

 각각 클래스도 인터페이스를 받도록 해준다. 

 안된다. 그냥 두 번 쓰면 되는데 괜히 고생하고 있는 거 같지만 이번에 해결하고 가면 나중에 수십 개가 됐을 때 쓸 수가 있을 듯싶다. 

public class Player : GetParticipant, IParticipant {

 

public class Enemy : GetParticipant, IParticipant {

 

 상속으로 처리하면 된다. 상속도 실전에선 처음 사용해 보는 듯싶다. 

 생각처럼 안된다 머가 문제 일까.

public class Enemy : Participant, IParticipant {

    int baseHP = 10;

    public override int BaseHP {get {return baseHP;} }

 

 이런식으로 만드니까 아예 사라져 버린다. 

public class Participant : MonoBehaviour, IParticipant {

    public virtual int BaseHP { get; set; }
}

 

 상속과 인터페이스가 답이 아닌 듯싶다. 

 다시 원점에서 다시 시도를 해보자.

    void Start()
    {
        foreach (var player in gameManager.playerList)
        {
            MakeHPBar (player);
        }

        foreach (var enemy in gameManager.enemyList)
        {
            MakeHPBar (enemy);
        }
    }

    void MakeHPBar (Participant paticipant)
    {
        var newClone =Instantiate
        (
            barObject,
            Camera.main.WorldToScreenPoint (paticipant.transform.position + posCorrection),
            Quaternion.identity, nonPostCanvas
                
        );
        var newBar = new BarNode ();
        newBar.baseTransform = paticipant.transform;
        newBar.barObject = newClone;
        barList.Add (newBar);
        newClone.GetComponent<HPBar>().maxHealth = paticipant.baseHP;
    }

    void Update()
    {
        foreach (var node in barList)
        {
            node.barObject.transform.position = Camera.main.WorldToScreenPoint (node.baseTransform.position + posCorrection);
        }
    }
}

public class Participant : MonoBehaviour {

    public int baseHP;
}

 

 이렇게 하면 되는데 문제는 계속 겹친다고 애러가 뜬다는 거다. 이걸 바꿔줘야 한다. 

public class Participant : MonoBehaviour {

    public virtual int BaseHP {get; set;}
}

 

public class Enemy : Participant {

    public int baseHP = 10;

    public override int BaseHP {get {return baseHP;} }

 

public class Player : Participant {
    
    public int baseHP = 10;
    public override int BaseHP {get {return baseHP;} }

 

 인터페이스를 쓰지 않고 상속만으로 해결했다. 인터페이스로 해결하려고 했는데 이게 모노비 헤이 비어를 상속을 시킬려니까 어떻게 하는지 모르겠더라. 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UIManager : MonoBehaviour {

    GameManager gameManager;
    public GameObject barObject;
    public Transform nonPostCanvas;
    public Vector3 posCorrection = new Vector3 (0f, 0f, 0f);
    public List<BarNode> barList = new List<BarNode>();

    void Awake()
    {
        gameManager = GetComponent<GameManager>();
    }

    void Start()
    {
        foreach (var player in gameManager.playerList)
        {
            MakeHPBar (player);
        }

        foreach (var enemy in gameManager.enemyList)
        {
            MakeHPBar (enemy);
        }
    }

    void MakeHPBar (Participant paticipant)
    {
        var newClone =Instantiate
        (
            barObject,
            Camera.main.WorldToScreenPoint (paticipant.transform.position + posCorrection),
            Quaternion.identity, nonPostCanvas
                
        );
        var newBar = new BarNode ();
        newBar.baseTransform = paticipant.transform;
        newBar.barObject = newClone;
        barList.Add (newBar);
        newClone.GetComponent<HPBar>().maxHealth = paticipant.BaseHP;
    }

    void Update()
    {
        foreach (var node in barList)
        {
            node.barObject.transform.position = Camera.main.WorldToScreenPoint (node.baseTransform.position + posCorrection);
        }
    }
}

public class Participant : MonoBehaviour {

    public virtual int BaseHP {get; set;}
}

public class BarNode
{
    public Transform baseTransform;
    public GameObject barObject;
}

 

 쉽게 만들꺼를 인터페이스랑 상속까지 알아보느라 꽤 복잡하게 만든 거 같다는 생각이 든다. 여하튼 체력 연동 문제를 해결했으니 다음으로 공격 명령을 만들어야 한다. 일단 가까이에 이동을 해서 행동력이 남았을 때 때리는 거랑 멀리서 때리는 걸 만들어야 한다. 모션도 넣고 말이다. 원거리 공격보다 조금 복잡한 듯싶다. 두 행 동력을 써서 이동을 했는데 행동력이 남아있지 않으면 그냥 종료가 되는데 이때는 행동력이 남았을 때 달려가서 공격하는 것과 달리 공격을 못하는 문제가 있긴 하다. 엑스컴의 경우에 이문제를 어떻게 처리했는지 게임을 해보면서 확인을 해봐야겠다. 일단 그건 뒤로 미루고 행동력이 남았을 때 다가가서 때리는 걸 만들고 근처에 있을 때 때리는 걸 만들어야 할 듯싶다. 아글고 나중에 근처만 오면 칼질하는 스킬도 만들어야 한다. 

 

 

 

 

'턴제제작' 카테고리의 다른 글

공격 모션 [영상]  (2) 2019.02.10
근접 전투 시스템  (0) 2019.02.08
체력 바  (0) 2019.01.25
이동 영역 시스템  (0) 2019.01.21
전투 시스템  (2) 2019.01.12

WRITTEN BY
아이고이아

,