我正在尝试在 Unity 中建立一个基于回合的战斗系统,我可以逐步完成角色的每个回合,并在玩家的回合中获得一些输入。我听说协程和 IEnumerators 可以帮助我做到这一点,但显然我没有完全掌握它是如何工作的,因为目前,我可以看到玩家角色的 currentHealth 一直下降到 0,甚至在它到达第一回合之前。
我希望有人能帮助我理解我哪里出了问题。这是我的 CombatSystem 课程:
public enum CombatState { START, ENEMYTURN, PLAYERTURN, WON, LOST }
public class CombatSystem : MonoBehaviour
{
public List<Character> currentEnemiesInCombat;
public CombatState state;
private GameController controller;
private void Awake()
{
controller = GetComponent<GameController>();
}
//Need to create an action response similar to openhiddenroom that will
//activate combat upon coming across revealed enemies, then CombatSystem
//will handle what happens below
public void StartCombat(PlayerCharacter playerCharacter, List<Character> triggeredEnemies)
{
//Bring up enemy illustrations, possibly resize canvas
//Roll initiative for each character
state = CombatState.START;
controller.ResizeDisplayTextForCombat();
controller.LogStringWithReturn("CombatSystem.cs DEBUG: YOU ARE IN COMBAT NOW");
SetUpCharacterInitiatives(playerCharacter, triggeredEnemies);
StartCoroutine(TurnBasedCombatRoutine(playerCharacter, triggeredEnemies));
}
//THIS NEEDS REWORKING FROM HERE DOWN-------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------------------------
IEnumerator TurnBasedCombatRoutine(PlayerCharacter playerCharacter, List<Character> triggeredEnemies)
{
var allCharactersInBattle = triggeredEnemies.Concat(new[] { playerCharacter }).OrderByDescending(c => c.initiative);
//Keep the fight going while the player is above 0 hp and enemies still remain
while (playerCharacter.currentHealth > 0 && triggeredEnemies.Count > 0)
{
foreach (Character character in allCharactersInBattle)
{
//Check if the character and playercharacter are still alive
//because we don't want enemies taking more turnns if the player is
//already dead
if (character.currentHealth > 0 && playerCharacter.currentHealth > 0)
{
controller.LogStringWithReturn("CombatSystem.cs DEBUG: " + character.charName + "'s turn");
//Perform actions for the current character's turn
//yield if it is the player's turn
if (character != controller.playerCharacter)
{
state = CombatState.ENEMYTURN;
PerformEnemyAction(character);
}
else
{
//NEED TO FIGURE OUT HOW TO MAKE THIS YIELD UNTIL A VALID USER INPUT
state = CombatState.PLAYERTURN;
controller.LogStringWithReturn("It is your turn...");
yield return null;
}
// Check for defeated enemies after each turn
triggeredEnemies.RemoveAll(enemy => isDead(enemy));
// Break out of the loop if all enemies are defeated
if (triggeredEnemies.Count == 0)
{
state = CombatState.WON;
controller.LogStringWithReturn("You have defeated all of your foes. It is safe to move forward.");
break;
}
}
}
}
EndCombat();
}
//Roll initiatives for all characters, including player
//Make sure they don't have the same initiative amounts
void SetUpCharacterInitiatives(PlayerCharacter playerCharacter, List<Character> CharactersInBattle)
{
System.Random rnd = new System.Random();
HashSet<int> usedInitiatives = new HashSet<int>();
// Set up playerCharacter as the first item in the CharactersInBattle List
playerCharacter.initiative = rnd.Next(1, 21);
usedInitiatives.Add(playerCharacter.initiative);
// Set up initiatives for other characters
for (int i = 0; i < CharactersInBattle.Count; i++)
{
int initiative;
do
{
initiative = rnd.Next(1, 21);
} while (usedInitiatives.Contains(initiative));
CharactersInBattle[i].initiative = initiative;
usedInitiatives.Add(initiative);
}
}
void PerformEnemyAction(Character currentEnemy)
{
System.Random rnd = new System.Random();
int attackRoll = rnd.Next(1, 21);
int damageRoll = rnd.Next(1, currentEnemy.maxDamage);
//if currentcharacter != playerCharacter, then make an attack roll
//against playerCharacter's ArmorClass
if (attackRoll > controller.playerCharacter.armorClass)
{
controller.playerCharacter.currentHealth = controller.playerCharacter.currentHealth - damageRoll;
controller.LogStringWithReturn($"{currentEnemy.charName} hits you {"[" + attackRoll + "]"} for {damageRoll} damage!");
//Check if player died from that hit
if(isDead(controller.playerCharacter))
{
state = CombatState.LOST;
controller.LogStringWithReturn("The Eternal Night washes over you... \nYOU ARE DEAD\nGAME OVER");
GameOver();
}
}
else if (attackRoll < controller.playerCharacter.armorClass)
{
controller.LogStringWithReturn($"{currentEnemy.charName} misses its attack!");
}
else
{
controller.LogStringWithReturn($"You narrowly dodge {currentEnemy.charName}'s attack!");
}
}
bool isDead(Character character)
{
if (character.currentHealth <= 0)
{
return true;
}
else
{
return false;
}
}
void GameOver()
{
//Need to have something happen if you lose combat
//Disable text input or allow a restart command
//Maybe send them to a game over room???
}
void EndCombat()
{
controller.ResizeDisplayTextPostCombat();
}
}
我认为将 PerformEnemyAction 更改为协程也可能有所帮助。我尝试过,但最终又重新使用它,因为错误仍然存在。
对我来说,你似乎永远不会碰到 else 语句,因为 if 语句正在询问是否字符!= Controller.playerCharacter。您的类型不匹配,因为 foreach 循环仅循环角色对象,而玩家角色的类型为“PlayerCharacter”。
C# 是一种强类型语言,您应该重新设计当前的方法,尝试声明具有列表/数组属性的无类型变量,否则您将陷入问题的世界。
有很多方法可以做到这一点,你可以创建一个堆栈并从中弹出对象,或者你也可以创建一个事件系统等。我个人会在玩家角色和角色类上都给出一个 'CombatOrder' int 变量。然后这两个类都可以有一个名为“AssignInitialCombatOrder(int order)”和“ProgressCombatOrder()”的方法,这样你就可以计算战斗状态变化时的顺序,将它们赋予每个对象,然后让它们的更新循环不执行任何操作,除非战斗顺序是 0 或其他什么,然后它重置回战斗顺序结束。不需要疯狂的产量或协程等