Hi everyone, I'm making a classic beat'em up game, in the style of Streets of Rage. It's my first game, so it's my first time programming game AI. This is maybe the most important part of the game, because it basically defines the way the game is going to be played. I have studied Software Engineering so I'm not a complete ignorant on the subject. I was thinking about making the enemies have a state machine, and basically make them change between states randomly. These states would be something like ATTACK, APPROACH, and PROWL. Apart from that randomness, the states would change according to key movements of the player in relation to the enemy. For example, in case the player got too close to the enemy, the enemy would probably attack. I though that maybe I can achieve an apparently complex behaviour by implementing a "simple" set of rules, which I think is probably what they did back in the day. I just wanted to get some feedback on these points, and maybe some ideas on the implementation. Not every enemy will have the same exact behaviour, although the main idea should be the same for everyone. Thanks!
Hi Mate, Here is some code I've written. I also attach a manual that goes a bit more indepth if you dont understand it. Code (csharp): using UnityEngine; using System.Collections; public abstract class EnemyBehaviour : MonoBehaviour { /*If you need guidance see the manual.txt, and if that didn't help you can email me at gustavbok@gmail.com!*/ /*Here you can add and remove states for the enemy, see the manual.txt for guidance!*/ public enum EnemyState { initializing, idle, sawPlayer, chasing, attacking, fleeing } /*This is the currentState of the Enemy, this is what you'll change in the child-Class*/ public EnemyState currentState; public GameObject playerReference; void Start () { currentState = EnemyState.initializing; } /*In here there is a switch-statement which handles which method that is going * to be updating, this is chosen by the currentState of the enemy. It is in here that you will add your own EnemyState.yourState-case and call for your own method below*/ public virtual void Update () { switch (currentState) { case EnemyState.initializing: /*filling in the player reference for easier access*/ playerReference = GameObject.Find ("Player"); currentState = EnemyState.idle; break; case EnemyState.idle: Idle(); break; case EnemyState.sawPlayer: SawPlayer(); break; case EnemyState.chasing: Chasing(); break; case EnemyState.attacking: Attacking(); break; case EnemyState.fleeing: Fleeing(); break; default: break; } } /*When you add your own methods here they need to be virtual, this is so you can in override them in your own class*/ public virtual void Idle() { } public virtual void SawPlayer() { } public virtual void Chasing() { } public virtual void Attacking() { } public virtual void Fleeing() { } } Manual(Not completed):
Enjoy I also make this into a new topic so it will be easier to find for others who search for finite state machines.
Hey man! Thank you for your answer, it's actually the way I was thinking about implementing it. I think I'm going to use a probabilistic state machine to approach the transition between states. So, with this approach, all the enemies will have the same set of states, but the probability of transitioning between them will change according to the type of enemy. For example, there'll be more aggressive enemies, which will attack more often, so the probability to transition to the ATTACK state will be higher for them. Doing it like this, I can just create a superclass for the AI and make each type of enemy inherit from it, simply changing the probabilities and maybe some other stuff if necessary.
Sounds like an really interesting approach! You can have that while using this basic script. Here, I made these two classes that inherits from the base class: Code (csharp): using UnityEngine; using System.Collections; public class Enemy1 : EnemyBehaviour { // Use this for initialization void Start () { } /*Now you will need to override the methods you want to use for the enemy, these can be * found in the bottom of the EnemyBehaviour-class*/ public override void Idle() { /*Add your idle-behaviour here*/ /*could be:*/ /*Update Patrolling the area*/ /* if in range of the player*/ /*Raycast toward player*/ /*if raycast hit - change currentState to sawPlayer*/ //currentState = EnemyState.sawPlayer; } public override void SawPlayer() { /*Add your SawPlayer-behaviour here*/ /*could be:*/ /* face the player */ /* play surprise animation */ /* when animation is done - change currentState to chasing*/ //currentState = EnemyState.chasing; } public override void Chasing() { /*Add your Chasing-behaviour here*/ /*could be:*/ /* if hp < 30 - change currentState to Flee*/ //currentState = EnemyState.fleeing; /* if not close enough to attack the player - move towards the player. It is here you choose * if the enemy is either melee or ranged */ /*else change currentState to attack*/ //currentState = EnemyState.attacking; } public override void Attacking() { /*Add your Attacking-behaviour here*/ /*could be:*/ /*if still close enough, do attack*/ /*else change to chase*/ } public override void Fleeing() { /*Add your Fleeing-behaviour here*/ /*could be:*/ /* if EnemyHp < 30*/ /*raycast to player and run the other way, simplest to implement*/ /*else*/ /*change state to chase*/ } } And for a filled out one, I made an ORC that has special behaviours in it: Code (csharp): using UnityEngine; using System.Collections; public class EnemyOrc : EnemyBehaviour { AudioSource attackSoundEffect; AudioSource lookSurprisedSoundEffect; bool hasSpottedPlayer; float reactionTime; GameObject weaponHitBox; Vector3 weaponStartPos; Vector3 weaponEndPos; float weaponSwingTimer; float weaponSpeed; float attackTimer; bool weaponsOut; float healthRegeneration; float healthRegenTimer; int hp; // Use this for initialization void Start () { hp = 10; reactionTime = 50; healthRegeneration = 2; healthRegenTimer = 0; weaponSpeed = 3; weaponsOut = false; attackTimer = 100; /*have not yet spotted player*/ hasSpottedPlayer = false; weaponSwingTimer = 0; } public override void Idle() { if(Vector3.Distance(transform.position,playerReference.transform.position) < 5) { currentState = EnemyState.sawPlayer; } } public override void SawPlayer() { if(!hasSpottedPlayer) { /*visualisation of enemy animation*/ transform.localScale = new Vector3(transform.localScale.x*2,transform.localScale.x*2,transform.localScale.x*2); hasSpottedPlayer = true; } if(reactionTime < 0) { reactionTime = 50; currentState = EnemyState.chasing; } { reactionTime -= 1; } } public override void Chasing() { transform.LookAt(playerReference.transform); if(Vector3.Distance(transform.position,playerReference.transform.position) > 2) { Vector3 moveTowards = playerReference.transform.position -transform.position; moveTowards.Normalize(); float dampening = 10; transform.position = new Vector3(transform.position.x+moveTowards.x/dampening, transform.position.y, transform.position.z+moveTowards.z/dampening); } else { currentState = EnemyState.attacking; } } public override void Attacking() { if(weaponHitBox != null) { weaponSwingTimer += Time.deltaTime* weaponSpeed; weaponHitBox.transform.position = Vector3.Slerp(weaponStartPos, weaponEndPos, weaponSwingTimer); } if(weaponsOut) { GameObject.Destroy(weaponHitBox); weaponHitBox = GameObject.CreatePrimitive(PrimitiveType.Cylinder); weaponHitBox.name = "weaponHitBox"; weaponHitBox.AddComponent<CapsuleCollider>(); weaponHitBox.transform.position = transform.position; weaponStartPos = weaponHitBox.transform.position; Vector3 towardsPlayer = playerReference.transform.position - transform.position; towardsPlayer.Normalize(); towardsPlayer *= 2; weaponEndPos = new Vector3( transform.position.x + towardsPlayer.x, transform.position.y + towardsPlayer.y, transform.position.z + towardsPlayer.z); weaponHitBox.transform.parent = transform; weaponHitBox.transform.LookAt(playerReference.transform); weaponHitBox.transform.rotation = Quaternion.Euler( new Vector3(weaponHitBox.transform.rotation.eulerAngles.x+90, weaponHitBox.transform.rotation.eulerAngles.y, weaponHitBox.transform.rotation.eulerAngles.z)); weaponsOut = true; } if(attackTimer < 0) { GameObject.Destroy(weaponHitBox); weaponSwingTimer = 0; currentState = EnemyState.chasing; weaponsOut = false; } else attackTimer -= 1; if(hp < 5) { GameObject.Destroy(weaponHitBox); weaponSwingTimer = 0; currentState = EnemyState.fleeing; } } public override void Fleeing() { Vector3 awayFromPlayer = transform.position - playerReference.transform.position; awayFromPlayer.Normalize(); transform.LookAt(transform.position + awayFromPlayer); if(hp < 5) { float dampening = 30; transform.position = new Vector3(transform.position.x+awayFromPlayer.x/dampening, transform.position.y, transform.position.z+awayFromPlayer.z/dampening); healthRegenTimer += Time.deltaTime; if(healthRegenTimer >= healthRegeneration) { Debug.Log("Enemy new hp: "+ hp); hp++; healthRegenTimer = 0; } } else { currentState = EnemyState.chasing; } } }
I totaly agree bigmisterb, but this thread was created before the other one and was the inspiration to make a totally new thread about it
I am stuck thinking about a good way to implement a "WANDER" or "PROWL" state... Do you guys have any ideas or suggestions?
These are some methods which can help you to make a better AI. I have made my AI using them. Stack-Based FSM http://gamedevelopment.tutsplus.com...ines-theory-and-implementation--gamedev-11867 http://gamedevelopment.tutsplus.com...ttern-using-steering-behaviors--gamedev-13638 Battle Circle AI http://gamedevelopment.tutsplus.com...heyre-fighting-lots-of-enemies--gamedev-13535