Trying to make some AI.. it kinda works, but how is my logic/way of coding doing?


(John Andersson) #1

Okay, so I’m trying to make some AI. I don’t want the enemies to just attack you blindly, I want them to move around. So far, they dash here and there, jump now and then and attack randomly. It works, but it’s a bit buggy. However, I don’t want you to help me make it work flawlessly. I just want to know if the way I’m doing it is good or bad. The logic, the amount of code written for this task. Is it too much? Should it be more refined? I know I’ve done a lot of stuff in the wrong fashion, like the attack timers and stuff.

It might be a weird topic, but now I don’t want answers on what to code, I want guidelines on how to code. Lemme link you the source code for an enemy I have made. I’m pretty new to coding, and this is the first game I’m working on, just so you know.

Warning: A lot of code

package  
{

import adobe.utils.CustomActions;
import flash.geom.Point;
import net.flashpunk.Entity;
import net.flashpunk.graphics.Spritemap;
    import net.flashpunk.graphics.Image;
    import net.flashpunk.utils.Input;
    import net.flashpunk.utils.Key;
    import net.flashpunk.FP;
import game_handling.Pause;
import Enemies;

public class Imp extends Enemies
{
	//Gfx
	[Embed(source = "assets/Imp_Full.png")] private const IMP:Class;
	public var sprImp:Spritemap = new Spritemap(IMP, 120, 144);

	//XP
	private var xpDrop:int = 3;
	
	//Stats
	private var hp:int = 100;
	public static const impStrength:uint = 10;
	
	private var mainSpeed:Number = 1;
	private var maxSpeed:Number = 4;
	private var mediumSpeed:Number = 2;
	private var jumpPower:Number = 14;
	
	//Movement, physics
	private var xSpeed:int = 0;
	private var ySpeed:Number = 1;
	private var gravity:Number = 0.3;
	private var hFriction:Number = 0.95;
	private var hgFriction:Number= 0.70;
	private var vFriction:Number = 0.99;
	private var jump:Boolean = false;
	private var dash:String;
	private var dashDecisionTimer:int = 60;
	private var shouldDecideDash:Boolean = false;
	private var dashCooldownTimer:int = 250;
	private var attackTimer:int = 45;
	private var isCloseEnough:Boolean = false;
	private var isCloseEnoughRange:int = 600;
	private var isCloseEnoughForDash:Boolean = false;
	private var isCloseEnoughForDashRange:int = 200;
	private var isAttacking:Boolean = false;
	private var goingLeftHero:Boolean = false;
	private var goingRightHero:Boolean = false;
	private var isDashing:Boolean = false;
	
	//Direction
	private var goingLeft:Boolean = false;
	private var goingRight:Boolean = false;
	private var direction:String;
	private var directionDecisionTimer:int = 60;
	private var shouldDecideDirection:Boolean = false;
	
	//Has weapon
	private var hasWeapon:Boolean = false;
	
	//Invincibility
	private var invincibilityCounter:int = 0;
	private var invincibilityCountDown:Boolean = false;
	
	//Sight
	private var sightRange:int = 480; //should be 980
	private var followHero:Boolean = false;
	
	//Roaming
	private var shouldRoam:Boolean = false;
	private var roam:String;
	private var roamDecisionTimer:int = 0;
	private var shouldDecideRoam:Boolean = false;
	
	//Timer values && Decision values
		//Timer values
	private var attackTimerVal:uint = 45;
	private var dashTimerVal:uint = 150;
	private var dirTimerVal:uint = 100;
	private var dashCooldownVal:uint = 250;
	
	private var trueDash:uint = 500;
	private var trueDashCD:uint = 500;
		
		//Decision values
		private var RoamDecisionVal:int = 100;
		private var dashDecisionVal:int = 45;
		private var dirDecisionVal:int = 100;
		private var attackDecisionVal:int = 35;
		
	//Nudge
	private var nudgeCounter:int = 9;
	private var nudgeCounterVal:int = 9;
	
	public function Imp(p:Point) 
	{
		//Spawn location
		var p:Point;
		x = p.x;
		y = p.y;
		
		//Gfx, hitbox
		graphic = sprImp;
		setHitbox(100, 128, -12, -16)

		
		//Animations
		sprImp.add("stand", [0], 0, true);
		sprImp.add("run", [9, 10, 11, 12, 13, 14, 15, 16, 17], 20, true);
		sprImp.add("crouch", [18, 19, 20, 21, 22, 23, 24, 25, 26], 30, true);
		sprImp.add("jump", [27, 28, 29, 30, 31, 32, 33, 34, 35], 30, true);
		sprImp.add("attack", [36, 37, 38, 39, 40, 41, 42, 43, 44], 15, false);
	}
	
	override public function update():void 
	{
	if(Pause.PAUSED) return;
	else
	{
		if (dashCooldownTimer == dashCooldownVal) isDashing = false;
		
		//Damage hero
		var e:Hero = collide("hero", x, y) as Hero;
		
		if (e)
		{
			if (sprImp.currentAnim == "attack")
			{
				if (sprImp.index >= 4 && sprImp.index <= 7)
				{
					var damageGiven:Number =  impStrength * HeroStats.armorPercentage;
							
					Hero.takeDamage(damageGiven);
				}
			}
		}
				
		//Debug trace
		
		//If hero is within range, follow him
			if (Hero.xPos < x) 
			{
				var leftDistance:Number = x - Hero.xPos;
				
				if (leftDistance <= sightRange) 
				{
					followHero = true; 
					sprImp.flipped = true;
				}else {
					followHero = false;
				}
			}else {
				var rightDistance:Number = x - Hero.xPos;
				
				if (rightDistance <= sightRange)
				{
					followHero = true; 
					sprImp.flipped = false;
				
					//Close enough?
					if (rightDistance >= 300) isCloseEnough = true;
					else isCloseEnough = false;
				}else {
					followHero = false;
				}
			}
		
		//Timers
			//Deciding if the timers should increment
				//Roaming
				if (!followHero) 
				{
					if (Math.floor(Math.random() * RoamDecisionVal) == 1) choose_roam();
				}
				
				//If the imp is following the hero
				if (followHero) 
				{ 
					//Dashing
					if (Math.floor(Math.random() * dashDecisionVal) == 1) 
					{
						dashDecisionTimer = 0;
					}
					
					//Direction
					if (Math.floor(Math.random() * dirDecisionVal) == 1) 
					{
						directionDecisionTimer = 0;
					}
					
					//Attack
					if (Math.floor(Math.random() * attackDecisionVal) == 1 && attackTimer == attackTimerVal)
					{ 
						attackTimer = 0; 
					}
									
					if (Math.floor(Math.random() * dashCooldownVal) == 0 && dashCooldownTimer == dashCooldownVal && trueDash == trueDashCD && isCloseEnoughForDash) 
					{
						choose_dash();
					}
				}
						
			//Executing the attack action
			if (attackTimer != attackTimerVal && isCloseEnough) 
			{
				isAttacking = true;
			}else {
				isAttacking = false;
				
			}

			if (directionDecisionTimer == 0) choose_direction();
				
			//Incrementing the timers
			if (attackTimer != attackTimerVal) attackTimer ++;
			if (dashDecisionTimer != dashTimerVal) dashDecisionTimer ++;
			if (directionDecisionTimer != dirTimerVal) directionDecisionTimer ++;
			
			if (dashCooldownTimer != dashCooldownVal) dashCooldownTimer ++;
		
		//Collision
		var sword:Weapon_Sword_Sword_of_the_AntiMage = collide("weapon", x, y) as Weapon_Sword_Sword_of_the_AntiMage;
		var hero:Hero = collide("hero", x, y) as Hero;
		
		//Getting hit by sword
		/*if (sword)
		{
			if (HeroStats.hp > 0)
			{
				if (invincibilityCounter == 0)
				{
					if (sword.sprSword.currentAnim == "attack")
					{
						var damageTaken:Number = HeroStats.strength * 10;
						var xLocation:Number = x + this.width / 2;
						var a:Number = Math.floor(Math.random() * (1 + x + this.width - x)) + x;
						var b:Number = Math.floor(Math.random() * (1 + y + 10 - y)) + y;
						
						hp -= damageTaken;
						invincibilityCountDown = true;
						
						FP.world.add(new DamageShower(a, b, damageTaken, "0xFF0000"));
					} 
	
				}
			}
		}*/
		
		//Invincibility 
		if (invincibilityCountDown)
		{
			invincibilityCounter ++;
				
			if (invincibilityCounter == 15)
			{
				invincibilityCounter = 0;
				invincibilityCountDown = false;
			}
		}
		
		//Death	
		if (y > 1080)
		{
			FP.world.remove(this);
		}
		
		if (hp <= 0)
		{
			var orb:XpOrb = new XpOrb(x, y, xpDrop);
			FP.world.add(orb);
			FP.world.remove(this);
		}
			
		//Roam
		if (!followHero)
		{
			if (roam == "Go left")
			{
				xSpeed = - mainSpeed; 
				goingLeft = true;
			}
			
			if (roam == "Go right") 
			{
				xSpeed = mainSpeed; 
				goingRight = true;
			}
			
			if (roam == "Stand still") xSpeed = 0;
			
			if (roam == "Jump") jump = true;
		}
		
		//Follow hero
		if (followHero)
		{
			//If hero is to the left, then right
			if (Hero.xPos < x)
			{
				if(x > Hero.xPos + 100)
				{
					xSpeed = 0 - ((x - Hero.xPos) / 55)
					goingLeftHero = true;
					
				}else {
					goingLeftHero = false;
					xSpeed = 0;
				}
				

			}
			else {
				if(x < Hero.xPos - 100)
				{
					xSpeed = (Hero.xPos - x) / 55
					goingRightHero = true;
				}else {
					goingRightHero = false;
					xSpeed = 0;
				}

			}
		}
		
		//Gravity, bitch
		
		if(!collide("ground", x, y + 1))
		{	
			ySpeed+=gravity;
		}
		
		//Facing whatever direction
		if (goingLeft) sprImp.flipped = true;
		if (goingRight) sprImp.flipped = false;
		
		//Attack
		if (attackTimer != attackTimerVal)
		{
			isAttacking = true;
		}
		if (attackTimer == attackTimerVal)
		{
			isAttacking = false;
		}

		//Attack animation
		if (isAttacking)
		{
			sprImp.play("attack"); 
		}else {
			if(goingLeft || goingRight) sprImp.play("run")
			if(!collide("ground",x,y)) sprImp.play("jump")
			if(isDashing) sprImp.play("run")
		}
		
		//Flip
		if (followHero)
		{
			if (x < Hero.xPos) sprImp.flipped = false;
			else sprImp.flipped = true;
		}
		
		//Move back if touching hero
		if (x > Hero.xPos)
		{
			if (x - Hero.xPos <= isCloseEnoughRange) isCloseEnough = true;
			else isCloseEnough = false;
			
			if (x - Hero.xPos <= isCloseEnoughForDashRange) isCloseEnoughForDash = true;
			else isCloseEnoughForDash = false;
			
			if (x - Hero.xPos <= 80) xSpeed = maxSpeed /2;
		}else {
			if (Hero.xPos - x <= isCloseEnoughRange) isCloseEnough = true;
			else isCloseEnough = false;
			
			if (Hero.xPos - x <= isCloseEnoughForDashRange) isCloseEnoughForDash = true;
			else isCloseEnoughForDash = false;
			
			if (Hero.xPos - x <= 80) xSpeed = - (maxSpeed / 2)
		}
		
		//True dash
		//Dash
					
		if (isDashing && followHero)
		{
			if (isCloseEnough)
			{
				if (dash == "Dash right") xSpeed = maxSpeed;
				if (dash == "Dash left") xSpeed = - maxSpeed;
				if (dash == "Dash left hard") xSpeed = maxSpeed*1.5;
				if(dash == "Dash right hard") xSpeed = -(maxSpeed *1.5);
			}
		}
		
		//True dash cd
		if (trueDash != trueDashCD) trueDash ++;
		
		if (followHero)
		{
			if (Math.floor(Math.random() * 150) == 1)
			{
				jump = true;
			}
			else jump = false;
		}
		
		if (jump)
		{
			if (collide("ground", x, y + 1))
			{
				ySpeed-=jumpPower;
			}
		}
		
		//Nudge
		if (nudgeCounter != nudgeCounterVal)
		{
			if (Hero.xPos > x)
				{
					xSpeed = - maxSpeed * 4;
				}else {
					xSpeed = maxSpeed * 4;
				}
				
			ySpeed -= jumpPower / 20;
			
			nudgeCounter ++;
		}
		
		//Adjust the movement
		adjustXPosition();
		adjustYPosition();
	}
	}
	
	override public function takeDamage(damageTaken:int):void
	{
		if (invincibilityCounter == 0)
		{
			nudgeCounter = 0;
			var xLocation:Number = x + this.width / 2;
			var a:Number = Math.floor(Math.random() * (1 + x + this.width - x)) + x;
			var b:Number = Math.floor(Math.random() * (1 + y + 10 - y)) + y;
				
			hp -= damageTaken;
			invincibilityCountDown = true;
						
			FP.world.add(new DamageShower(a, b, damageTaken, "0xFF0000", 40));	
		}
	}
	
	private function adjustXPosition():void 
	{
		for (var i:int=0; i<Math.abs(xSpeed); i++) {
            if (! collide("ground",x+FP.sign(xSpeed),y)) {
                x += FP.sign(xSpeed);

            } else {
                xSpeed = 0;
                break;
            }
        }
	}
	
	private function adjustYPosition():void {
				
		for (var i:int=0; i<Math.abs(ySpeed); i++) {
			if (! collide("ground", x, y + FP.sign(ySpeed))) {
				y += FP.sign(ySpeed);
			} else {
				ySpeed=0;
				break;
				}
			}
	}
	
	private function choose_roam():void
	{
		roam = FP.choose("Go left", "Go right", "Stand still", "Stand still", "Stand still", "Jump");
	}
	
	private function choose_dash():void
	{
		if (dashCooldownTimer == dashCooldownVal)
		{
			dashCooldownTimer = 0;
			isDashing = true;
		}
		
		dash = FP.choose("Dash right", "Dash left", "Dash right hard", "Dash left hard");
	}
	
	private function choose_direction():void
	{
		direction = FP.choose("Left", "Right", "Stand still")
	}
}

}

(Bora Kasap) #2

Oh my eyes!

What to say, that’s familiar to me… But i’m not going to do the same in my next project…

Also, i think coding some AI is really looks like everything else… AI is the main subject of coding… So, firstly, i’m going to tell things about coding directly:

  • Try to create functions for specific behaviors and then call these functions from somewhere in update() function instead of creating these functions inside update() function. Clean code saves your time and enchances your creativity.
  • And something rare, hard to configure, but nice to implement after you built it: You can create “Behavior Entities” and let them “Control Targeted Unit’s Behaviors”… I can name that “AI Buff for units” instead of “Own AI of any unit”.