🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Java Game; Mystery of Extra Unwanted Projectiles being Spawned

Started by
2 comments, last by BigSpider 3 years, 11 months ago

I encountered a big sticking point in a Java game I've been coding over the summer. I was working on it pretty steadily until mid-June but since then I've been stuck on a single thing. My character is supposed to be able to charge and shoot qi blasts, and that part works, but *some of the time* I get one or more extra unwanted qi blasts. Actually it happens pretty frequently.

Unlike the single qi blast that is supposed to be created, these ones start out fully charged and they fire off in random directions (instead of in the direction of the mouse). Those are the clues to this mystery I guess. The single qi blast that is created with or without extra ones charges correctly and fires off toward the mouse like it is supposed to. Another clue: it seems like there are a few frames in between the desired qi blast being spawned and the unwanted ones spawning. Like two or three frames - but I'm not entirely sure, so I don't want to throw anyone off.

For my code I started off with the Cherno's game engine that tried to replicate “Realm of the Mad God” and altered and built on it. The qi blast is a QiBlast object which is a type of Projectile object which is a type of Entity object. I am trying to not post all my code here because I have looked at it, gone away from it, and looked at it again - I think the problem could be resolved without me posting all my code, but I will post as needed. Here is the QiBlast class:

public class QiBlast extends Projectile {
	
	SpriteSheet spritesheet_qb_b;
	AnimationTimer chargeTimer;
	private Mob mob;
	private Mouse mouse;
	private boolean init = false;
	public boolean charging = true;
	private boolean fired = false;
	
//	private int x, y;
	
//	private double nx, ny;
	private double speed, range, damage;
	
//	private double angle;
	
	private double dx;
	private double dy;
	
//!!!
// no double or triple qi blasts
// bug where it shoots where it shot last time
	
	//  range, speed, and damage should also be parameters
	public QiBlast(int x, int y, Mob shooter, Mouse mousey)  {
		super(x, y, shooter);
		range = 200;
		speed = 4;
		//damage = 20;
		
		mouse = mousey;
		
		mob = shooter;
		
		//sprite = Sprite.rotate(Sprite.projectile_arrow, angle);
		//rateOfFire = 15;
		
		spritesheet_qb_b = new SpriteSheet("qiblast_blue.png", 94, 63, 3, 2, 30, 30, 1);
		
		sprite = spritesheet_qb_b.getSprites()[3];
		
		//nx = speed * Math.cos(angle);
		//ny = speed * Math.sin(angle);

	}
	
	public synchronized void update()  {
		//super.update();
		/*
		if (level.tileCollision((int) (x + nx), (int) (y + ny), 7, 5, 4))  {
			level.add(new ParticleSpawner((int) x, (int) y, 44, 50, level));
			remove();
		}*/
		//change it so you have to hold the mouse button to charge it - if you let go before it is charged, it disappears and makes a sound
		
		if (!init)  {
		init();
		init = true;
		}
		
		if (charging) {
			//  If the player rotates, re-calculate the angle the projectile fires at
			
		if (mouse.mouseDown == false)  {
			mob.setCharging(false);
			remove();
		}
		
		//System.out.println("kakakaka");
		this.sprite = spritesheet_qb_b.getSprites()[chargeTimer.getFrames()];
		
		if (chargeTimer.getFrames() == 5)
			charging = false;
		//mob.setCharging(false);
				
		this.x = mob.getX() + 5;
		this.y = mob.getY() + 5;
		}
		
		if (!charging && mouse.mouseDown == true)  {
			//
			x = mob.getX() + 5;
			y = mob.getY() + 5;
		}
		
		// need events and stuff for this
		if (!charging && mouse.mouseDown == false && !fired)  {
			
			fired = true;
			
			PointerInfo a = MouseInfo.getPointerInfo();
			Point b = a.getLocation();
			int getx = (int) b.getX();
			int gety = (int) b.getY();
			
			dx = getx - (Game.screen_width / 2);
			dy = gety - (Game.screen_height / 2);
			//angle = Math.atan2(dy, dx);	
			//nx = speed * Math.cos(angle);
			//ny = speed * Math.sin(angle);	
			
			double invlen = 1.0f / Math.sqrt(dx * dx + dy * dy);
			nx = speed * dx * invlen;
			ny = speed * dy * invlen;
			
			mob.setCharging(false);
			
			move();
		}
		
		// !!! also, why are the extra qi blasts fully charged?
		// !!! maybe they are old objects that aren't being destroyed properly?
		if (!charging && fired)  {
			move();
		}
	}
	
	private void move()  {
	//if (!level.tileCollision(x, y, nx, ny, 7))  {
		x += nx;
		y += ny;
	//}
		xOrigin = mob.x;
		yOrigin = mob.y;
		if (distance() > range) remove();	
	}
	
	public void init()  {
		chargeTimer = new AnimationTimer();
		chargeTimer.anim_init(100, 5);
		//update();
	}
	
	
	private double distance()  {
		double dist = 0;
		dist = Math.sqrt(Math.abs((xOrigin - x) * (xOrigin - x) + (yOrigin - y) * (yOrigin - y)));
		return dist;
	}
}
//and here is a snippet from the Player class:
private void updateShooting()  {
		
		if (m_input.mouseDown == true)  {
			//System.out.println("kakakaka");
			if (qb_charging == false)  {
			qb_charging = true;
			
			System.out.println("kakakaka");
			
			shoot_qb(x, y, this, m_input);
			//System.out.println("kakakaka");
			//charging = true;
			}
		}
		
		for (int i = 0; i < projectiles.size(); i++)  {
			//System.out.println("kakakaka");
			projectiles.get(i).update();
		}
	}
//and here from the parent of Player, Mob:
	protected synchronized void shoot_qb(int x, int y, Mob mob, Mouse mouse) {
		if (qb_charging == false)  {
		Projectile p = new QiBlast(x, y, mob, mouse);
		projectiles.add(p);
		}
		//System.out.println("kakakaka");
	}
Advertisement

There are some serious unneeded complications: you should

  • remove unneeded dependencies (e,g, QiBlast → mouse, QiBlast→shooter) that can only facilitate mistakes
  • remove redundant, overly complex and probably inconsistent data (e.g. charging AND qb_charging AND mob.setCharging)
  • replace an overly complex update method that tries to do many different things with self-contained meaningful commands (e.g. "release shot if charged long enough", “move towards point X Y”)
  • replace flags that can be used incorrectly (init, charging, fired) with a proper state machine with explicit transitions between mutually exclusive states that would be easier to prove correct
  • use a self-managed variable for the charge level instead of an AnimationTimer

I'd try to distinguish the charging Qi shot (attached to a character) and the fired Qi shot (a flying projectile) as two separate entitiy types because they have a completely different behaviour and the latter never reverts to the former, but I'm not sure it would work well in the context of your engine.

Omae Wa Mou Shindeiru

@LorenzoGatti Thank you for your advice, I will look over my code again and see what I can improve!

I had an idea of what might be wrong earlier today, but now I'm not sure. I'm wondering if there is something funky with the array list programming. It might be possible to have empty slots in the array list so when I iterate from 0 to projectiles.size, I miss some at the end. I am not sure that this is how array lists work though (does the add method add only after the final element, or will it in addition fill in any empty slots before adding after the final element?).

Perhaps if I consider this but also incorporate your suggestions I will be able to get my code working again so I can move on with my project. Thank you again. I can program but I'm not an expert in game programming so posts like yours are really helpful to me.

This topic is closed to new replies.

Advertisement