It is currently Tue Nov 13, 2018 5:27 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 12 posts ] 
Author Message
PostPosted: Mon Aug 13, 2018 1:03 pm 
Offline

Joined: Mon Nov 21, 2011 3:53 pm
Posts: 40
Good day everyone.

After A year of school I thought I'd give programming for the nes another shot.
So far it's been going very good. Got some basic moving sprites and backgrounds.

I want to make a simple horizontal shooter but i'm kinda stuck on the following:

Say I spawn an enemy ship, I set some values using the .rs directive in nesasm to reserve some space in ram to hold it's height and width etc.
Now say that ship gets hit by a bullet and gets destroyed, how do I free up that memory that I have reserved for that ship?

It might be obvious that I'm coming from and object-oriented world and I'm basically missing my c++ new and delete statements.

I have the feeling i'm thinking all wrong, any advice for this noob?


Top
 Profile  
 
PostPosted: Mon Aug 13, 2018 1:16 pm 
Offline
User avatar

Joined: Mon Jan 03, 2005 10:36 am
Posts: 3135
Location: Tampere, Finland
.rs is equivalent to making it a global (or static) variable in C++. So you cannot free it.

What's usually done is to allocate some fixed number of objects (say 20, or 32), each with a fixed number of state bytes (e.g., 8). There should be a byte in the state that indicates what the type of the object is (e.g., an "enemy ship", or a "bullet", or "unused"). Then, when you want to allocate a new object, you scan the object list for a free object and set it to the desired type. In your object handling code you scan through the list and call appropriate handlers (based on object type) for each item in the list. When you want to destroy the enemy ship (or whatever), you'd set the object type back to "unused".

You can speed this up by using a "free list", basically a linked list of free object slots. This means you don't have to scan through the whole list to find a free object, you can just pick one from the end of the linked list.

_________________
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi


Top
 Profile  
 
PostPosted: Mon Aug 13, 2018 1:53 pm 
Offline
User avatar

Joined: Thu Apr 23, 2009 11:21 pm
Posts: 940
Location: cypress, texas
Hi klono,

Um, I've never used nesasm but, from my experience with assembly language, variables (groups of bytes in RAM) can be declared before you assemble your game. After assembly, I don't believe it's possible to destroy a variable or create a new one. However, tokumaru told me how he uses temporary variables. To create a temporary variable:
Code:
;your variable declaration section in zeropage goes here
;afterwards
LocalVariables4vblank
LocalVariables4scrollland
;etc.

;then in my vblank file (I'm using the asm6 assembler)
.enum LocalVariables4vblank
  tE .dsb 2
  t14 .dsb 2
.ende

Then tE can be used anywhere below that variable declaration section. Caution: Lets say your zeropage variables end with $0082. That would cause all of the LocalVariables4 labels to be initialized to #$0083. Therefore, tE would be assembled at locations $0083 and $0084 (because my temporary variables all have a size of 2 bytes... that's just me, feel free to use whatever size you want... it's your game. :)). The tempory (local) variables should only be used when you do not require the value at a later frame, or even for a short while because many, I guess, other local variables will be assigned seats at those same memory locations and so the value may vanish whenever a store to that memory location is processed. (The value wouldn't vanish if the store, sta stx or sty, assigned the exact same value to that memory location.) Hope this helps you. :)


Top
 Profile  
 
PostPosted: Mon Aug 13, 2018 2:00 pm 
Offline
User avatar

Joined: Thu Apr 23, 2009 11:21 pm
Posts: 940
Location: cypress, texas
thefox is way more advanced than me. :) But, tokumaru's local variables have helped me so much! :mrgreen: :D


Top
 Profile  
 
PostPosted: Mon Aug 13, 2018 2:04 pm 
Offline

Joined: Sun Mar 27, 2011 10:49 am
Posts: 266
Location: Seattle
thefox's post is very good, listen to it.

if you'd like to deepen your understanding a little bit, one thing that might be worth thinking about is that there's nothing especially "magical" about new and delete in C++; they aren't that different from malloc() and free() in C, which are just ordinary library functions that run ordinary code. All they're doing (ignoring some details around modern operating systems and memory protection and stuff) is managing chunks of memory in an address space. It might be edifying to consider and research how and why they're implemented the way they are, and what are some of the pros and cons of them vs other memory management strategies.


Top
 Profile  
 
PostPosted: Mon Aug 13, 2018 2:13 pm 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 2329
Location: DIGDUG
One thing. You can access objects faster in asm if you make each attribute a separate array. So, rather than an array of objects (with x,y,type,state,etc)...you have an array of x's, an array of y's, an array of types, etc.

You load the object number into the X register, and you can access it's attributes by using X as the index to each array. LDA x_array, X. Do something. LDA type_array, X.

BTW, I don't do this myself, but I've seen others do this.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Mon Aug 13, 2018 2:22 pm 
Offline
User avatar

Joined: Wed Apr 02, 2008 2:09 pm
Posts: 1251
Basically you create an object "class". Say it has xPos, and yPos only for simplicity. In a higher level language you'd create an array of class objects. On NES it's better on the CPU to create an array of each element.
The higher level way:
Code:
const int objectnum = 16;
struct object{
   int xPos;
   int yPos;
};
object objects[objectnum];

The NES way
Code:
const int objectnum = 16;
int xPos[objectnum];
int yPos[objectnum];


Unlike a higher level thing, it's best to just have a fixed maximum number of objects. If you want a maximum of 16 objects with 16 elements, that's 256 bytes.
Code:
objectnum = 16
xPos .rs objectnum
yPos .rs objectnum

To access their RAM, you have their object number in either X, or Y, and access their element using ,y or ,x addressing like so.
Code:
inc yPos,x
lda objectstate,x

If you have a byte that says whether or not they're alive, on a really basic level you could have a type byte. If type is say... equal to zero, it means the object is dead. So destroying an object is as simple as storing zero to its type byte. (Maybe zeroing out the rest of its RAM as well.) Creating an object is as simple as looking for an object number that has a type byte equal to zero, and then changing the type byte of that object to the type of object you want to create. (And initializing its RAM to a state that type of object expects.)

Here's a long post I wrote on the subject with some more advanced methods: viewtopic.php?f=10&t=13061&p=152230&hilit=linked#p152230

_________________
https://kasumi.itch.io/indivisible


Top
 Profile  
 
PostPosted: Mon Aug 13, 2018 2:37 pm 
Offline

Joined: Mon Nov 21, 2011 3:53 pm
Posts: 40
Wow so many great tips in such little time!
Thanks everyone!


Top
 Profile  
 
PostPosted: Mon Aug 13, 2018 2:39 pm 
Offline

Joined: Mon Nov 21, 2011 3:53 pm
Posts: 40
adam_smasher wrote:
thefox's post is very good, listen to it.

if you'd like to deepen your understanding a little bit, one thing that might be worth thinking about is that there's nothing especially "magical" about new and delete in C++; they aren't that different from malloc() and free() in C, which are just ordinary library functions that run ordinary code. All they're doing (ignoring some details around modern operating systems and memory protection and stuff) is managing chunks of memory in an address space. It might be edifying to consider and research how and why they're implemented the way they are, and what are some of the pros and cons of them vs other memory management strategies.


Do you have some resources you would recommend on the subject?


Top
 Profile  
 
PostPosted: Mon Aug 13, 2018 2:50 pm 
Offline

Joined: Mon Nov 21, 2011 3:53 pm
Posts: 40
thefox wrote:
.rs is equivalent to making it a global (or static) variable in C++. So you cannot free it.

What's usually done is to allocate some fixed number of objects (say 20, or 32), each with a fixed number of state bytes (e.g., 8). There should be a byte in the state that indicates what the type of the object is (e.g., an "enemy ship", or a "bullet", or "unused"). Then, when you want to allocate a new object, you scan the object list for a free object and set it to the desired type. In your object handling code you scan through the list and call appropriate handlers (based on object type) for each item in the list. When you want to destroy the enemy ship (or whatever), you'd set the object type back to "unused".

You can speed this up by using a "free list", basically a linked list of free object slots. This means you don't have to scan through the whole list to find a free object, you can just pick one from the end of the linked list.


Funny how the fixed number of objects approach was in my head but it didn't 'feel' right to do it like that.
The free list idea is also a good tip!


Top
 Profile  
 
PostPosted: Mon Aug 13, 2018 3:15 pm 
Offline
User avatar

Joined: Thu Sep 15, 2016 6:29 am
Posts: 783
Location: Denmark (PAL)
Here's a shorter answer. You don't "free up memory", you just stop refering to it, and then when another object needs it, you initialize it with the values for that object.


Top
 Profile  
 
PostPosted: Mon Aug 13, 2018 3:58 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 6948
Location: Canada
There are some cases where you might want to reuse/reassign a static allocation, like if your game changes to a different mode.

If you know they're never going to be needed at the same time, you can still reassign the same memory statically. There's no encapsulation or type safety here, if you .rs a byte of memory, that's a byte you can use any way you want, and you can give the same byte many names/labels.

In NSEASM you can use .rsset to reset the .rs reservations to the address you want to overlap, and then create a new set of reservations on top. In CC65 you could do something similar with overlapping segments. Or you can just directly create an alias for an old reservation, if that's convenient ("new_name = old_name").

When doing this, though, you should probably have a clear plan of how you want to lay out memory. Know what's supposed to be "global" and survives the mode switch, and what isn't. Have a good idea of how much space each should take up, and maybe leave some extra room just in case you need it later. Though, this is something you might want to think about even if you're not trying to overlap stuff. If RAM is scarce it pays to manage your RAM budget. ;)


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 12 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group