Shooting game question #2

Started by Squinky, Mon 26/08/2013 02:54:11

Previous topic - Next topic

Squinky

I've been working on a shooting game, one where the bad guys come from one side of the screen and you are positioned on the other, firing a weapon at them.
Currently, the weapon firing is just if you click on the enemy character, you hit. Otherwise you miss. This is simple and works decent, but I was considering another option.
This option would be that when you clicked on the enemy, the missile would be a character or object that would move rapidly across the screen to the target. I think it would add another level of depth to the game, and I could possibly achieve cool things like:
1.    Allowing high powered bullets to continue through enemies into others.
2.   Bow type weapons would look much better.
3.   Automatic fire would be better
4.   Lasers, I have ideas for lasers.
My problem is that all of the enemies are already characters, and characters and objects are limited per screen as I have been told. I could see maybe twenty enemy characters on screen at once as things stand.
I used AGS for a good bit back in the day, and understand a good bit but I am still learning. So, any ideas on how to pull this off without characters would be wonderful. I've read over the manual and nothing is popping out to me.

Any help is appreciated, thank you.

Andail

Upon firing, just turn on an object and have it move toward where you clicked, preferably by using the tween module (so you can have it accelerate/slow down if needed - a rocket may pick up speed, and an arrow will probably slow down a bit.) Don't block it.

In repeatedly_execute, you check if the object is colliding with a character.

So
Code: ags

function on_mouse_click(MouseButton button) {
  if (mouse == eMouseLeft){
    oProjectile.Visible = true;
    oProjectile.Move (mouse.x, mouse.y, 10, eNoBlock, eAnywhere);
  }
}

function repeatedly_execute() {
  if (GetCharacterAt (oProjectile.X, oProjectile.Y) != null){
    Character * target = GetCharacterAt (oProjectile.X, oProjectile.Y);
    // then script "target"'s fate here...
  }
}

Haven't tested this, and was a bit stressed while writing, so it may contain huge errors!

DoorKnobHandle

#2
Ultimately, you don't want to use characters or objects at all. In order to be completely free of any arbitrary limits (except performance at some point), you need to 'raw draw' your enemies, projectiles etc. (which, nowadays, is handled by dynamic sprites and drawing surfaces). Here's how I do this nowadays:

First of all, create a new script/header file by right-clicking the 'Scripts' node and clicking on 'New script'. Call it 'Renderer' and enter this into the new header file (Renderer.ash):

Code: AGS

struct Renderer
{
	import static DynamicSprite *registerRenderTarget(GUI *interface);
	import static DrawingSurface *getRenderTargetSurface(DynamicSprite *renderTarget);
	import static bool updateRenderTarget(GUI *interface, DynamicSprite *renderTarget);
	import static void destroyRenderTarget(DynamicSprite *renderTarget);
};


And paste this into the script file (Renderer.asc):

Code: AGS

static DynamicSprite *Renderer::registerRenderTarget(GUI *interface)
{
	if (interface != null)
	{
		if (interface.BackgroundGraphic > 0)
			return DynamicSprite.CreateFromExistingSprite(interface.BackgroundGraphic);
		else
			return DynamicSprite.Create(interface.Width, interface.Height);
	}
	
	return null;
}

static DrawingSurface *Renderer::getRenderTargetSurface(DynamicSprite *renderTarget)
{
	if (renderTarget == null)
		return null;
		
	return renderTarget.GetDrawingSurface();
}

static bool Renderer::updateRenderTarget(GUI *interface, DynamicSprite *renderTarget)
{
	if (interface == null || renderTarget == null)
		return false;
		
	interface.BackgroundGraphic = renderTarget.Graphic;
	
	return true;
}

static void Renderer::destroyRenderTarget(DynamicSprite *renderTarget)
{
	if (renderTarget != null)
		renderTarget.Delete();
}


As you can probably tell, this now gives you a Renderer structure to play with that includes four helpful functions. Here's how to use the new functionality:

- You are going to be drawing on GUIs with this code (in other words: GUIs are used as 'render targets'), that's a flexible and fast way to render and allows you to seperate the things you render into layers, move/rotate it and so on. For the time being, you should probably just create a new empty fullscreen interface by right-clicking on the 'GUIs' node, clicking on 'New GUI' and setting it up. You could call the GUI 'gRenderTarget' for example.

- You need a global variable (a pointer to a dynamic sprite) that represents your render target. For example, you could go to your global script (GlobalScript.asc) and add this line at the very top of the file:

Code: AGS

DynamicSprite *renderTarget;


- on game_start in GlobalScript.asc (ie. when your game starts, before you begin drawing anything), you need to register the render target GUI as follows (insert your global variable and GUI name if they differ):

Code: AGS

renderTarget = Renderer.registerRenderTarget(gRenderTarget);


- in repeatedly_execute in GlobalScript.asc (or whenever you want to draw something), this is how to render (note that you don't have to call 'surface.Clear()' if you don't want to clear what was previously on the render target from when you last rendered):

Code: AGS

DrawingSurface *surface = Renderer.getRenderTargetSurface(renderTarget);
surface.Clear();
	
// here you can use the surface.Draw*() functions to render
	
surface.Release();
Renderer.updateRenderTarget(gRenderTarget, renderTarget);


And then, when you exit the game, you should call Renderer.destroyRenderTarget to clean up the used memory:

Code: AGS

Renderer.destroyRenderTarget(renderTarget);


This might seem a bit daunting at first but it will enable you to have a high number of things on screen at the same time. Now you'd need to change your current code to not use characters or objects but instead draw a sprite instead. If you don't have that yet, you definitely need a struct for your enemies and your projectiles, an array of instances of each of these structs and then you can give each struct a render function for example that renders the enemy/projectile to the render target. Some pointers:

Code: AGS

// in Projectile.ash

struct Projectile
{
    int x, y;

    import void render(DrawingSurface *surface);
};

#define MAX_PROJECTILES 1024

Projectile projectile[MAX_PROJECTILES];


// in Projectile.asc

void Projectile::render(DrawingSurface *surface)
{
    surface.DrawImage(this.x, this.y, /*sprite slot*/ 12);
}


// in GlobalScript.asc

void repeatedly_execute()
{
    DrawingSurface *surface = Renderer.getRenderTargetSurface(renderTarget);
    surface.Clear();
	
    int i = 0;
    while (i < MAX_PROJECTILES)
    // loop through each projectile
    {
        // render the projectile
        projectile[i].render(surface);
        i++;
    }

    surface.Release();
    Renderer.updateRenderTarget(gRenderTarget, renderTarget);
}


Please note that the above pointers are rough and will not work when simply copy/pasted. You still need to import/export the array and keep track of which projectiles out of the 1024 are 'active' (ie. on the screen) and so on.

Hopefully this helps you and isn't too confusing! :p

Squinky

Thanks Andail and Doorknob.

Andail's advice looks like the easiest road for me, but I going to set some time aside to try and learn what you posted Doorknob. It looks promising but a little confusing at first :)

Thanks again!

SMF spam blocked by CleanTalk