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
And paste this into the script file (Renderer.asc):
Code: AGS
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
- 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
- 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
And then, when you exit the game, you should call Renderer.destroyRenderTarget to clean up the used memory:
Code: AGS
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
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
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):
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):
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:
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):
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):
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:
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:
// 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