플레이어의 큐브,생산자,대포만 변하는거 같아서 AI 적에게도 스테이지가 끝날 때 마다 제약을 걸어줄 것이다.
AI는 선택하지 않고, 종류별로 있는 데이터들 중 무작위로 몇가지 골라서 효과를 받는다.
우선 계획은 ConstraintManager에서 AI 전용 효과를 몇가지 뽑고, ConstraintData와 StatManager에서 시전자라는 변칙을 주어 플레이어와 적이 다른 스탯을 가지도록 할 것이다.

StatManager
using UnityEngine;
public class StatManager : MonoBehaviour
{
public int startResource; // 초기 자원량
public int resourceYield; // 자원 생산량
public int cannonDamage; // 대포 공격력
public float cannonAttackSpeed; // 대포 공격속도
public int cubeHealth; // 큐브 체력
public float resourceSpeed; // 자원 생산속도
// 적
public int enemyStartResource; // 초기 자원량
public int enemyResourceYield; // 자원 생산량
public int enemyCannonDamage; // 대포 공격력
public float enemyCannonAttackSpeed; // 대포 공격속도
public int enemyCubeHealth; // 큐브 체력
public float enemyResourceSpeed; // 자원 생산속도
private static StatManager instance;
public static StatManager Instance { get { return instance; } }
private void Awake()
{
if(instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
initStartResource = startResource;
initResourceYield = resourceYield;
initCannonDamage = cannonDamage;
initCannonAttackSpeed = cannonAttackSpeed;
initCubeHealth = cubeHealth;
initResourceSpeed = resourceSpeed;
initEnemyStartResource = enemyStartResource;
initEnemyResourceYield = enemyResourceYield;
initEnemyCannonDamage = enemyCannonDamage;
initEnemyCannonAttackSpeed = enemyCannonAttackSpeed;
initEnemyCubeHealth = cubeHealth;
initEnemyResourceSpeed = enemyResourceSpeed;
}
public void StartingResource(int value, Caster caster)
{
ref int result = ref caster is InputController ? ref startResource : ref enemyStartResource;
result = Mathf.Max(20, result + value);
}
public void CannonDamage(int value, Caster caster)
{
ref int result = ref caster is InputController ? ref cannonDamage : ref enemyCannonDamage;
result = Mathf.Max(5, result + value);
}
public void ResourceYield(int value, Caster caster)
{
ref int result = ref caster is InputController ? ref resourceYield : ref enemyResourceYield;
result = Mathf.Max(5, result + value);
}
public void CannonAttackSpeed(float value, Caster caster)
{
ref float result = ref caster is InputController ? ref cannonAttackSpeed : ref enemyCannonAttackSpeed;
result = Mathf.Max(0.5f, result + value);
}
public void CubeHealth(int value, Caster caster)
{
ref int result = ref caster is InputController ? ref cubeHealth : ref enemyCubeHealth;
result = Mathf.Max(10, result + value);
}
public void ResourceSpeed(float value, Caster caster)
{
ref float result = ref caster is InputController ? ref resourceSpeed : ref enemyResourceSpeed;
result = Mathf.Max(0.05f, result + value);
}
}
먼저 적의 스탯을 사용하기 위해 변수를 모두 선언해준다. 여기서 기존에 있던 스탯 프로퍼티들을 모두 함수의 형태로 바꿔주었다. 시전자에 따라서 ref로 변수의 값을 변화시키는 형식이다.
ConstraintData
시전자가 둘 이상이기 때문에 myCaster라는 시전자 변수를 넣어준다.
using UnityEngine;
public abstract class ConstraintData : ScriptableObject
{
public Caster myCaster;
public abstract string sentence { get; } // 설명 문자열
public abstract void Execute();
}
ConstraintManager
public void Constraint()
{
constraintPanel.gameObject.SetActive(true);
int index = 0;
for (int i = 0; i < buttons.Length; i++) // 각 버튼들
{
// 버려진 버프 데이터 채우기
buffDatas.AddRange(buffTemp);
buffTemp.Clear();
// 버려진 너프들 다시 채우기
nuffDatas.AddRange(nuffTemp);
nuffTemp.Clear();
for (int j = 0; j < 2; j++) // 버튼의 문장
{
bool isTemp = false;
if (j <= 0) // 버프 문장 선택
{
index = Random.Range(0, buffDatas.Count);
if(i == 1) // 두번째 버튼의 버프일때
{
while(buttons[0].buffData.GetType() == buffDatas[index].GetType())
{
buffTemp.Add(buffDatas[index]);
buffDatas.Remove(buffDatas[index]);
if(buffDatas.Count != 0)
{
index = Random.Range(0, buffDatas.Count);
}
else // 더이상 다른 버프가 없을 때
{
index = Random.Range(0, buffTemp.Count); // 버려진 버프중 하나 가져오기
isTemp = true;
Debug.Log("더이상 다른 버프가 없어요 2");
break;
}
}
}
buffDatas[index].myCaster = player;
if(!isTemp)
{
buttons[i].buffData = buffDatas[index];
}
else
{
buttons[i].buffData = buffTemp[index];
}
}
else if(j >= 1) // 너프 문장 선택
{
isTemp = false;
index = Random.Range(0, nuffDatas.Count);
if(i == 0)
{
while (buttons[i].buffData.GetType() == nuffDatas[index].GetType())
{
nuffTemp.Add(nuffDatas[index]);
nuffDatas.RemoveAt(index);
if (nuffDatas.Count != 0)
{
index = Random.Range(0, nuffDatas.Count);
}
else
{
isTemp = true;
index = Random.Range(0, nuffTemp.Count);
break;
}
}
}
else
{
while (buttons[i].buffData.GetType() == nuffDatas[index].GetType() || buttons[0].nuffData.GetType() == nuffDatas[index].GetType())
{
nuffTemp.Add(nuffDatas[index]);
nuffDatas.RemoveAt(index);
if(nuffDatas.Count != 0)
{
index = Random.Range(0, nuffDatas.Count);
}
else
{
isTemp = true;
index = Random.Range(0, nuffTemp.Count);
break;
}
}
}
nuffDatas[index].myCaster = player;
if (!isTemp)
{
buttons[i].nuffData = nuffDatas[index];
}
else
{
buttons[i].nuffData = nuffTemp[index];
}
}
}
buttons[i].DataSetUp();
}
buffDatas.AddRange(buffTemp);
buffTemp.Clear();
nuffDatas.AddRange(nuffTemp);
nuffTemp.Clear();
StartCoroutine(FadeUI(constraintPanel));
}
각 데이터에 시전자를 넣어줘야 한다.
적의 제약에 관해서 새롭게 코드를 작성해준다.
void Start()
{
GameManager.Instance.gameFinish += Constraint;
GameManager.Instance.gameFinish += EnemyConstraint;
// GameManager.Instance.gameStart += Constraint;
}
private void OnDisable()
{
// GameManager.Instance.gameStart -= Constraint;
GameManager.Instance.gameFinish -= Constraint;
GameManager.Instance.gameFinish -= EnemyConstraint;
}
public void EnemyConstraint() // 적의 제약을 실행
{
int level = GameManager.Instance.enemyLevel;
int buffNum = level * 2 - 1; // 버프의 개수
int nuffNum = 5 - buffNum; // 너프의 개수
// 1 : 1
// 2 : 3
// 3 : 5
for(int i = 0; i < buffNum; i++)
{
int index = Random.Range(0, buffNum);
ConstraintData data = enemyBuffs[index];
data.myCaster = enemy;
data.Execute();
Debug.Log(data.name);
}
for(int i = 0; i < nuffNum; i++)
{
int index = Random.Range(0, nuffNum);
ConstraintData data = enemyNuffs[index];
data.myCaster = enemy;
data.Execute();
}
}
적의 레벨에 따라서 Easy면 버프 1개, 너프 4개, Normal이면 버프 3, 너프 2, Hard면 버프 5개를 가진다.
데이터의 함수 호출
각 Scriptable 데이터들의 함수 호출에서 myCaster를 넣어 호출해준다.
BasicResource
using UnityEngine;
[CreateAssetMenu(menuName = "Scriptable/ConstraintData/BasicResource")]
public class BasicResource : ConstraintData
{
public int value; // 증가할 자원량
public override string sentence => $"Starting Resources {value}";
public override void Execute()
{
Debug.Log($"기초 자원량 {value} 증가");
StatManager.Instance.StartingResource(value, myCaster);
}
}
CannonAttackSpeed
using UnityEngine;
[CreateAssetMenu(menuName = "Scriptable/ConstraintData/CannonAttackSpeed")]
public class CannonAttackSpeed : ConstraintData
{
public float attackSpeed;
public override string sentence => $"Cannon Attack speed {attackSpeed}";
public override void Execute()
{
Debug.Log($"대포 공격속도 {attackSpeed} 변화");
StatManager.Instance.CannonAttackSpeed(attackSpeed, myCaster);
}
}
ResourceSpeed
using UnityEngine;
[CreateAssetMenu(menuName = "Scriptable/ConstraintData/ResourceSpeed")]
public class ResourceSpeed : ConstraintData
{
public float speed;
public override string sentence => $"Resource Production Speed {speed * 10}";
public override void Execute()
{
Debug.Log($"생산 속도 {speed} 증가");
StatManager.Instance.ResourceSpeed(speed, myCaster);
}
}
ResourceYield
using UnityEngine;
[CreateAssetMenu(menuName = "Scriptable/ConstraintData/ResourceYield")]
public class ResourceYield : ConstraintData
{
public int value;
public override string sentence => $"Resource Value {value}";
public override void Execute()
{
Debug.Log($"생산자의 생산력이 {value} 증가했습니다");
StatManager.Instance.ResourceYield(value, myCaster);
}
}
CannonDamage
using UnityEngine;
[CreateAssetMenu(menuName = "Scriptable/ConstraintData/CannonDamage")]
public class CannonDamage : ConstraintData
{
public int addDamage; // 추가되는 데미지
public override string sentence => $"Cannon {addDamage} Damage ";
public override void Execute()
{
Debug.Log($"대포 데미지 {addDamage} 증가");
StatManager.Instance.CannonDamage(addDamage, myCaster);
}
}
CubeHealth
using UnityEngine;
[CreateAssetMenu(menuName = "Scriptable/ConstraintData/CubeHealth")]
public class CubeHealth : ConstraintData
{
public int addHealth;
public override string sentence => $"Cube Health {addHealth}";
public override void Execute()
{
Debug.Log($"큐브 체력 {addHealth} 업");
StatManager.Instance.CubeHealth(addHealth, myCaster);
}
}
이제 StatManager에서 사용했던 것처럼 스탯을 사용하는 스크립트에 조건을 추가해주면 된다.
Cannon
void Start()
{
if(myCaster is InputController)
{
attackDelay = StatManager.Instance.cannonAttackSpeed;
}
else if(myCaster is EnemyAI)
{
attackDelay = StatManager.Instance.enemyCannonAttackSpeed;
}
~~~~~~~~~ 중략~~~~~~~~~
}
IEnumerator FireBall()
{
~~~~~~~~~중략~~~~~~~~
while (true)
{
~~~~~~~~~~~~~ 중략 ~~~~~~~~~
if(myCaster is InputController)
{
prefab.GetComponent<CannonBall>().damage = StatManager.Instance.cannonDamage;
}
else if(myCaster is EnemyAI)
{
prefab.GetComponent<CannonBall>().damage = StatManager.Instance.enemyCannonDamage;
}
~~~~~~~중략~~~~~~~~
}
}
}
ResourceProducer
void Start()
{
if(myCaster is InputController)
{
delayTime = StatManager.Instance.resourceSpeed;
resourceValue = StatManager.Instance.resourceYield;
}
else if(myCaster is EnemyAI)
{
delayTime = StatManager.Instance.enemyResourceSpeed;
resourceValue = StatManager.Instance.enemyResourceYield;
}
~~~~~~~~~~~~~~중략~~~~~~~~~~~
}
Cube
void Start()
{
if(myCaster is InputController)
{
hp = StatManager.Instance.cubeHealth;
}
else if(myCaster is EnemyAI)
{
hp = StatManager.Instance.enemyCubeHealth;
}
}
EnemyAI
void DataSetUp() // 데이터를 확인해서 적용하는 함수
{
~~~~~~~~~~~~~~~~~~~~~~~~~
totalResource = StatManager.Instance.enemyStartResource + enemyData.totalResource;
~~~~~~~~~~~~~~~~~~~~~~~~~~
}
초기 자원량 같은 경우, 기존 데이터가 가지고 있는 초기 자원량에 StatManager의 추가적인 자원량을 더해 사용한다.
특수 조건에서 제약 막기
튜토리얼 중이거나, 적에게 패배했거나, 마지막 라운드라서 제약 시스템을 사용하면 안될 조건에서의 처리를 구현한다.
public void Constraint()
{
if(GameManager.Instance.winner == GameManager.Winner.ENEMY) // 적이 이겼을 때 선택 막기
{
return;
}
if(GameManager.Instance.enemyStage == 5) // 게임이 끝날을때 선택 막기
{
return;
}
if(GameManager.Instance.myState == GameManager.GameState.TUTORIAL) // 튜토리얼 상태일 때 선택 막기
{
return;
}
~~~~~~~~~~~~~~~~중략~~~~~~~~~~~~
}
public void EnemyConstraint() // 적의 제약을 실행
{
if (GameManager.Instance.winner == GameManager.Winner.ENEMY) // 적이 이겼을 때 선택 막기
{
return;
}
if (GameManager.Instance.enemyStage == 5) // 게임이 끝날을때 선택 막기
{
return;
}
if (GameManager.Instance.myState == GameManager.GameState.TUTORIAL) // 튜토리얼 상태일 때 선택 막기
{
return;
}
~~~~~~~~~~~~~~~~~~~~~중략~~~~~~~~~~~~~~~~~~
}
제약 처리코드 앞에 조건문 3개로 특수조건에서의 발동을 막는다.
'GameDev > Cubidom' 카테고리의 다른 글
| Cubidom - 프로젝트 빌드 (0) | 2026.05.16 |
|---|---|
| Cubidom - 최종 수정 및 해상도 설정 (0) | 2026.05.13 |
| Cubidom - 제약 시스템 (0) | 2026.05.07 |
| Cubidom - AI 시스템 보완 (Utility AI) (0) | 2026.05.03 |
| Cubidom - 디테일 추가 및 버그 수정 2 (0) | 2026.04.30 |