로마리네의 소녀마도사

  • PC
  • 2026년 05월 27일
  • 0 comments

전작과의 비교

데미지 로직

2016년 코드

public void PhysicalDamaged(long damage)
{
	float EVR = Random.Range(0f, 100f);//낮을수록 회피 성공
	float CRR = Random.Range(0f, 100f);//낮을수록 치명타 성공
	if ((MobLV - Status_Singleton.Instance.Stat_LV) >= 6) {
		EVR = 0f;
	} else if ((Status_Singleton.Instance.Stat_LV - MobLV) >= 11) {
		CRR = CRR - 50f;
	}
	if (EVR >= EV) {
		if (CRR <= Status_Singleton.Instance.Stat_CR) {
			damage = damage*3/2;
		} 
		damage -= PD;

		if (damage < 0) {
			damage = 0;
		}


		HP -= damage;

		if (CRR <= Status_Singleton.Instance.Stat_CR) {
			damageSTRP += "치명타! " + damage.ToString ("0.#") + "\n";
		} else {
			damageSTRP += damage.ToString ("0.#") + "\n";
		}



		GameObject.FindWithTag ("Canvas").SendMessage("dealplus",damage);
		if (HP <= 0) {
			GameObject.FindWithTag ("Canvas").SendMessage ("EXP_Get", ToGetEXP);
			StartCoroutine ("DiedThisMob");
		}
	} else {
		damageSTRP += "Miss" + "\n";
	}
	damageTMP.text = damageSTRP;
	StartCoroutine ("DamageTMIEP");
	/*
	GameObject.FindWithTag ("").SendMessage("PhysicalDamaged",ATK);
	을 이용하여 호출하고 사용한다.
	*/
	if (HP > 0) {
		hpbar.transform.localScale = new Vector3 (((float)HP /(float) Max_HP) * hpbarX, hpbarY, hpbarZ);
		if (IsBoss == true) {
			BossHP.fillAmount = (float)HP /(float) Max_HP;
		}
	} else {
		hpbar.transform.localScale = new Vector3 (0f * hpbarX, hpbarY, hpbarZ);
		if (IsBoss == true) {
			BossHP.fillAmount = 0f;
		}
	}
}

public void MagicalDamaged(long damage)
{
	bool EleCri = false;
	bool EleEv = false;
	float EVR = Random.Range(0f, 100f);
	float CRR = Random.Range(0f, 100f);
	if ((MobLV - Status_Singleton.Instance.Stat_LV) >= 6) {
		EVR = 0f;
	} else if ((Status_Singleton.Instance.Stat_LV - MobLV) >= 11) {
		CRR = CRR - 50f;
	}
	if (EVR >= EV) {
		if (CRR <= Status_Singleton.Instance.Stat_CR) {
			damage = damage * 3/2;
		} 
		damage -= MD;
		switch (Monster_element) {
		case 1: //불속방어
			switch (element) {
			case 2: //물
				damage = damage * 3/2;
				EleCri = true;
				break;
			case 3: //풀
				damage = damage /2;
				EleEv = true;
				break;
			}
			break;
		case 2: //물속방어
			switch (element) {
			case 1: //불
				damage = damage /2;
				EleEv = true;
				break;
			case 3: //풀
				damage = damage *3/2;
				EleCri = true;
				break;
			}
			break;
		case 3: //풀속방어
			switch (element) {
			case 2: //물
				damage = damage /2;
				EleEv = true;
				break;
			case 1: //불
				damage = damage *3/2;
				EleCri = true;
				break;
			}
			break;
		case 4: //빛속방어
			switch (element) {
			case 4: //빛
				damage = damage /2;
				EleEv = true;
				break;
			case 5: //어둠
				damage = damage *3/2;
				EleCri = true;
				break;
			}
			break;
		case 5: //어둠속방어
			switch (element) {
			case 4: 
				damage = damage *3/2;
				EleCri = true;
				break;
			case 5:
				damage = damage /2;
				EleEv = true;
				break;
			}
			break;
		}
		if (damage < 0) {
			damage = 0;
		}


		HP -= damage;


		GameObject.FindWithTag ("Canvas").SendMessage ("dealplus", damage);
		if (EleCri == true) {
			if (CRR <= Status_Singleton.Instance.Stat_CR) {
				damageSTRM += "속성효율적! 치명타! " + damage.ToString ("0.#") + "\n";
			} else {
				damageSTRM += "속성효율적! " + damage.ToString ("0.#") + "\n";
			}
		} else if (EleEv == true) {
			if (CRR <= Status_Singleton.Instance.Stat_CR) {
				damageSTRM += "속성비효율! 치명타! " + damage.ToString ("0.#") + "\n";
			} else {
				damageSTRM += "속성비효율! " + damage.ToString ("0.#") + "\n";
			}
		} else {
			if (CRR <= Status_Singleton.Instance.Stat_CR) {
				damageSTRM += "치명타! " + damage.ToString ("0.#") + "\n";
			} else {
				damageSTRM += damage.ToString ("0.#") + "\n";
			}
		}

		HP -= damage;
		if (HP <= 0) {
			GameObject.FindWithTag ("Canvas").SendMessage ("EXP_Get", ToGetEXP);
			StartCoroutine ("DiedThisMob");
		}
	} else {
		damageSTRM += "Miss" + "\n";
	}
	damageTMM.text = damageSTRM;
	StartCoroutine ("DamageTMIEM");
	if (HP > 0) {
		hpbar.transform.localScale = new Vector3 (((float)HP /(float) Max_HP) * hpbarX, hpbarY, hpbarZ);
		if (IsBoss == true) {
			BossHP.fillAmount = (float)HP /(float) Max_HP;
		}
	} else {
		hpbar.transform.localScale = new Vector3 (0f * hpbarX, hpbarY, hpbarZ);
		if (IsBoss == true) {
			BossHP.fillAmount = 0f;
		}
	}
	/*
	.SendMessage("elementtype",속성값);
	.SendMessage("MagicalDamaged",ATK);
	을 이용하여 호출하고 사용한다.
	*/
}

2017년 코드

public bool TakeDamage(RMRN_DamageInfo damageInfo, RMRN_CrowdControl cc = null)
	{
		if(isDie)
		{
			return false;
		}
		if(currentInvinLevel > damageInfo.invincibleLevel)
		{
			return false;
		}

		DamageState damageState = DamageState.None;

		int def = (int)(defend * (1.0f - damageInfo.defendIgnore));
		int pow = damageInfo.attack - def;

		int evadeRate = evade - damageInfo.accuracy;

		bool isEvade = Random.Range(0, 100) < evadeRate;

		bool isCritical = false;
		if (isEvade)
		{
			pow = (int) (pow * 0.1f);
			damageState = DamageState.Evade;
		} 
		else
		{
			float criticalRate = damageInfo.criticalRate;
			isCritical = Random.Range(0.0f, 1.0f) < criticalRate;
			if(isCritical)
			{
				damageState = DamageState.Critical;
			}
			pow = (int)(isCritical ? pow * damageInfo.criticalDamage : pow);
		}

		pow = (int)(pow * (1.0f + 0.5f * CheckElementAdvantage(damageInfo.element, element)));

		pow = Mathf.Max(1, pow);

		if (cc != null)
		{
			ccList.Add(cc);
			cc.Init(this);
			cc.Start();
			onChangeCCEvent.Invoke(cc, true);
		}

		currentHp -= pow;

		currentHp = Mathf.Clamp(currentHp, 0, MaxHp);

		onTakeDamageEvent.Invoke(damageState, pow);
		onHPUpdateEvent.Invoke(currentHp);

		if (currentHp <= 0)
		{
			isDie = true;

			for (int i = ccList.Count - 1; i >= 0; i--)
			{
				ccList[i].End();
				onChangeCCEvent.Invoke(ccList[i], false);
				ccList.Remove(ccList[i]);
			}

			onDieEvent.Invoke();
		}

		return true;
	}

Before
– 데미지/UI/속성 계산이 혼합되어있음
– 중복 로직 존재

After
– 기능 분리
– 중복 제거
– 가독성 향상

몬스터

2017년

2026년

Before
– 단순 충돌 기반 공격
– 패턴 시스템 부재

After
– UI기능 분리
– 드롭 기능 모듈화
– FSM 구조 적용
– 레벨에 따른 몬스터 스탯 자동 연산

인벤토리

2016년 코드

public class Inventory : MonoBehaviour 
{
	void Start ()
	public void Itemcheck(int itemgetcode)
	public void ItemRemove(int itemgetcode)
	public void maneget (long howmuch)
	public void Select_Item(int slotcode)
	public void UseItem(int slotcode)
	public void Alert200OK()
	public void Alert200No()
	public void EquipSlotMenu()
	public void EquipSlot(int slotnum)
	public void QuitEquipSlotMenu()
	public void QuickSlot5()
	public void QuickSlot6()
	public void QuickSlot7()
	public void TouchedItemIsThis(GameObject item)
	public void MyItemValIsThis(int val)
	public void MyItemcodeIsThis(int itemcode)
	public void CouponGet(int itemcode)
	void itemRQLVSearcher(int itemcodename)
	IEnumerator CloseAlert()
	public void OnApplicationPause(bool paused) 
	void Inventory_Save()
	public void Inventory_Close()
	void ItemEffect(int ItemEffectCode)
	public void ResetPanel()
	public void page1()
	public void page2()
	public void page3()
	public void page4()
	public void trashMenuOn()
	public void trashMenuOff()
	public void RemoveItem()
}
2017년 RMRN_InventoryUI.cs 요약

public class RMRN_InventoryUI : RMRN_UIPanel
{
	public override void OnOpen()
	private void DrawItem()
	private void ShowItemInfo(string itemID)
	public override void OnClose()
	public void ClearItem()
	public override void Escape()
	public override void Init()
	public void Use()
	public void QuickRegist()
	public void UpdateInventory()
	public void Sort(int sortType)
}
2017년 RMRN_Inventory.cs 요약

public class RMRN_Inventory : MonoBehaviour
{
	public void Init()
	public void RegistItem(RMRN_Item item)
	public void AddItem(string itemID, int amount)
	public void AddItem(string itemID, string instanceID, int enchantLevel = 0)
	public void AddItem(RMRN_EquipableItem equipableItem)
	public bool UseItem(string id, int amount = 1)
	public bool RemoveItem(string id, int amount)
	public void RemoveItem(string id)
	public RMRN_Item GetItem(string id)
	public void ResetInventory()
	public string[] GetItemInstanceIDs(string id)
}

Before
– UI/세이브/아이템/쿠폰시스템 처리 혼합
– 단일 클래스 구조

After
– UI와 데이터 분리
– 저장 로직 분리
– 아이템의 효과 관련 로직 분리

아이템 사용

2017년

2017년 아이템 처리부분 요약
public void MyItemcodeIsThis(int itemcode)
{
  bool itemget = false;
  bool Itemchecker = true;
  for (dexcode = 0; dexcode < 40; dexcode++) {
    if (ItemCode [dexcode] == itemcode) {
      if (ItemCode [dexcode] >= 200 && ItemCode [dexcode] < 300) 
      {
      } 
      else 
      {
        if (itemcode >= 200 && itemcode < 300) {
          ItemValueCode [dexcode] += tempValueItem;
          TouchedItem.gameObject.SendMessage ("Used");
          Itemchecker = false;
          itemget = true;
          switch (itemcode) {
          case 200:
            ItemPACode [dexcode] = 1;
            ItemMACode [dexcode] = 1;
            break;
          case 201:
            ItemPACode [dexcode] = 1;
            ItemMACode [dexcode] = 2;
            break;

          ...

          case 299:
            ItemPACode [dexcode] = 3067;
            ItemMACode [dexcode] = 6334;
            break;
          }
        } else {
          ItemValueCode [dexcode] += tempValueItem;
          TouchedItem.gameObject.SendMessage ("Used");
          Itemchecker = false;
          itemget = true;
          break;
        }
      }
    }
  }
  if (Status_Singleton.Instance .InvenIsFull == false&&Itemchecker == true) 
  {
    for (dexcode = 0; dexcode < 40; dexcode++) 
    {
      if (ItemCode [dexcode]== 0) 
      {
        ItemCode [dexcode] = itemcode;
        ItemValueCode [dexcode] += tempValueItem;
        if (ItemCode [dexcode] >= 200 && ItemCode [dexcode] < 300) 
        {
          ItemLVCode [dexcode] = 0;
        } 
        else 
        {
          ItemLVCode [dexcode] = -1;
        }
        itemRQLVSearcher (dexcode);
        ItemLVRQCode [dexcode] = itemLVSearcher;
        itemget = true;
        TouchedItem.gameObject.SendMessage ("Used");
        slot[dexcode].sprite = Resources.Load<Sprite>("Item/"+ItemCode[dexcode].ToString("000"));
        break;
      }
    }
    Status_Singleton.Instance .InvenIsFull = true;
    for (dexcode = 0; dexcode < 40; dexcode++) 
    {
      if (ItemCode [dexcode] == 0) 
      {
        Status_Singleton.Instance.InvenIsFull = false;
      }
    }
  }
  if (itemget == true) 
  {
    StopCoroutine ("CloseAlert");
    AlertPanel.SetActive (true);
    AlertMessage.text = Resources.Load<TextAsset> ("Item/name/" + itemcode.ToString ("000")).text + "를 획득하였습니다.";
    StartCoroutine ("CloseAlert");
  } 
  else 
  {
    StopCoroutine ("CloseAlert");
    AlertPanel.SetActive (true);
    AlertMessage.text = "인벤토리가 가득차서 더 이상 획득할 수 없습니다.";
    StartCoroutine ("CloseAlert");
  }
  Inventory_Save ();
}

2026년

Scriptable Object Inspector & Resources

RMRN_Item Base Class
public abstract class RMRN_Item : ScriptableObject
{
    public string itemID;

    public int amount;

    public ItemType type;
    public ItemRarity rarity;

    public abstract float Effect();
}

Before
– switch 기반 처리
– 확장 및 유지보수 어려움
– 코드 가독성 나쁨
After
– Scriptable Object 기반 데이터 처리
– 상속 기반 확장 구조
– 비교적 코드 가독성 향상


개발 기간

2025/04/20 ~ 제작중


구현 사항

  • 전투 시스템
    • 플레이어 FSM
    • 몬스터 FSM
    • 스킬 및 아이템
    • 장비 및 강화
    • 스테이터스 시스템
    • 상호작용 시스템
  • 데이터 시스템
    • SaveData
      • 세이브데이터 복제방지 암호화
    • 인벤토리
    • 퀘스트
  • UI
    • UI 시스템
    • 튜토리얼
    • 대화모드
    • 다국어 시스템
    • 스킬 트리 UI
    • 도움말 UI
    • 설정 UI
  • 기타
    • Sound Transit 시스템

플레이 영상