This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

"Creating" and "Destroying" sprites

"Creating" and "Destroying" sprites
by on (#151494)
So I've been working on a game and I'd like to add enemies. From what I understand, to "get rid of a sprite", you just set it's y position to #$FE. And I'm assuming I'd load a screen's respective sprites in a similar fashion to loading the screen itself:

An address table of the byte tables of sprite oam for each room. Maybe a byte table for the number of enemies on each screen. The sprites for a given room are indirectly loaded to OAM until the number for how many sprites to load is reached. Right?

But I guess I'm a little confused on how metasprites are "destroyed". Say the player shoots the 4th sprite of a 4-sprite metasprite. How should I go about telling the program "Okay, get rid of these 3 other sprites too"?

Also how would I tell the game which sprites do what? Like B Enemy should do C, D Enemy should do E. When you touch powerup F it shouldn't take away health, but disappear, and some variable be set to 1?
Re: "Creating" and "Destroying" sprites
by on (#151496)
I just rebuild the entire OAM buffer every frame, so there's nothing to eliminate. I just add any sprites that need to appear that frame, in a pseudo-randomized order to create priority flicker.
Re: "Creating" and "Destroying" sprites
by on (#151505)
You're thinking at a very low level. The game engine should not be concerned about OAM entries. The game engine is supposed to manage (spawn, destroy, move, etc.) objects, not OAM entries. That's what a metasprite system is for. Most games just call a function that draws each object, and this function is the one that inspects the object and uses its position and other attributes to generate all the OAM entries necessary to draw it. If the object has been destroyed and doesn't exist anymore, there will simply be no request to draw it.

Abstracting these hardware details from the game engine is essential if you want to be able to build more complex games. Directly managing OAM entries is OK for pong and other simple games, but once you have scrolling, dynamic objects and other more advanced things, it becomes nearly impossible to manage the hardware aspects in such a hardcoded way. You'll go crazy if you try.

The sub-systems of a game are meant to turn the hardware into a "black box", so that the engine can communicate with it at a higher level. A scrolling system, for example, allows the game engine to request that a specific column of the level map be rendered to the screen, without having to worry about name tables or attribute tables. The sub-system will take care of that.
Re: "Creating" and "Destroying" sprites
by on (#151533)
tokumaru wrote:
You're thinking at a very low level. The game engine should not be concerned about OAM entries. The game engine is supposed to manage (spawn, destroy, move, etc.) objects, not OAM entries. That's what a metasprite system is for. Most games just call a function that draws each object, and this function is the one that inspects the object and uses its position and other attributes to generate all the OAM entries necessary to draw it. If the object has been destroyed and doesn't exist anymore, there will simply be no request to draw it.

I'm still pretty new to programming for the entertainment system, how exactly would I implement an object system like this?

On the low end should I have a variable that keeps track of where in RAM it's free to draw new sprites, and on the high end have different subroutines for each object like DrawGoomba and DrawKoopaTroopa that already have their sprites and attributes hard-coded into the routines, and then just have their coordinates for each screen in .db's? Or am I going about this entirely wrong?

I appreciate your help by the way.
Re: "Creating" and "Destroying" sprites
by on (#151550)
NES, Commodore 64, Atari 7800, though these systems have very different display list strategies, it's all the same at the game logic level. Keep a separate array of "actors", each with its own (X, Y) coordinates, and run all physics on the actors. For example, the player character is an actor, the enemy is an actor, each bullet is an actor, and each explosion is an actor.

After you have moved everything, translate those actors into entries in whatever passes for a display list on a particular platform.
  • On NES, you'd take one actor and turn it into a bunch of OAM entries at relative X, Y positions from the actor's position. You guessed right that you keep track of how much space is left in the local copy of OAM so that you can add more hardware sprites as needed.
  • On 7800, the screen is divided into several horizontal strips called zones. You write the actor's sprite into the display list for each zone that the actor covers. For the top and bottom zones, use the "holey DMA" bit to skip the areas above and below the actor.
  • On C64, you'd take each actor and turn it into a job in your raster interrupt job table to move one or two of the 8 hardware sprites on the scanline just before the top of the actor is reached. Some games have a job for each sprite, while others use 20-line-tall "zones" and spit sprites into those zones as needed, and the simplest games just use one set of 8 sprites for the whole screen.
You might not understand the other platforms' display paradigms, but my point is that actor movement usually doesn't need to be tightly coupled to displaying.

Unless your actors are very different in nature, such as missiles vs. explosions vs. smoke particles, you usually don't hardcode different actor types into separate routines. Instead, you can make a lookup table of (X offset, Y offset, tile number) pointers and read metasprite definitions from that.
Re: "Creating" and "Destroying" sprites
by on (#151554)
This is a very simplified example of how to draw a sprite:
Code:
; variables:
oam: page-aligned 256 byte OAM buffer
oam_pos: starts at 0 at the beginning of the frame, fills up as sprites are added
sprite_ptr: 2-byte pointer to metasprite data
sprite_x: X coordinate for metasprite
sprite_y: Y coordinate for metasprite

draw_metasprite:
   sprite_ptr = pointer to metasprite data looked up from table
   ldy #0
   ldx oam_pos
   @tile_loop:
      lda (sprite_ptr), Y
      iny
      cmp #END_OF_METASPRITE
      beq @loop_end
      clc
      adc sprite_y
      sta oam+0, X ; store Y coordinate
      lda (sprite_ptr), Y
      iny
      sta oam+1, X ; store tile
      lda (sprite_ptr), Y
      iny
      sta oam+2, X ; store attribute
      lda (sprite_ptr), Y
      iny
      clc
      adc sprite_x
      sta oam+3, X ; store X coordinate
      inx
      inx
      inx
      inx ; advance to next 4-byte OAM entry
      jmp @tile_loop
   @loop_end:
   stx oam_pos ; remember position for next sprite
   rts

There are a bunch of other things you probably want to do in your sprite routine (this is far from complete), but the basic concept is to keep a variable that tells you where in the buffer to add each new sprite (in this example "oam_pos"). Each time you call this function, a new sprite gets added to the end of the list. This way you can build your sprites in whatever order, or as many as you need.