It is currently Sun Oct 22, 2017 11:30 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 100 posts ]  Go to page Previous  1, 2, 3, 4, 5, 6, 7  Next
Author Message
PostPosted: Sun Nov 06, 2016 11:27 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1787
Location: DIGDUG
I think I had about 20 objects, if you count bullets, in my Spacy Shooty example game. In C. Although most of the time consuming parts were written in ASM. (collision detection) (object rendering).

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


Top
 Profile  
 
PostPosted: Sun Nov 06, 2016 11:57 am 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1405
psycopathicteen wrote:
I never want to design an entire game around using only 4 objects at once. The hardware is already limited as it is, why make it even more limited?

The game wasn't consciously designed around it. It just happened to be this way.
I guess I could easily add another character and still not run into a lag, but the game is already hard enough as it is.
(The game uses auto-scrolling, so you cannot just stop. Most of my testers told me that it's very hard. Adding another opponent would make it even harder.)
As you can see in the screenshot, there's still tons of rendering time, so no, the game is not at the NES's limits in the slightest. I could add more stuff if I wanted. If I removed the digitized voice sample, I would even still have tons of ROM space for completely new enemy movement patterns.

Besides, are there ever more than three opponents on screen at once in "Super Mario Bros."? Even "Ninja Gaiden" usually doesn't have more than four opponents at once, so it's not like commercial platformers are usually crowded with enemies while having only three screams "amateur".

dougeff wrote:
Although most of the time consuming parts were written in ASM. (collision detection).

What's time-consuming with collision detection?
Code:
collision =
   !(
         playerLeft > npcRight
      || playerRight < npcLeft
      || playerTop > npcBottom
      || playerBottom < npcTop
   );

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Sun Nov 06, 2016 12:58 pm 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1787
Location: DIGDUG
Because collision checks increase exponentially as objects increase.

I have 20 objects, but I'm actually doing 80 collision checks per frame. Thus, it is prudent to optimize this function.

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


Top
 Profile  
 
PostPosted: Sun Nov 06, 2016 1:34 pm 
Offline

Joined: Sun Mar 27, 2016 7:56 pm
Posts: 137
Well, it's exponential if everything can collide with everything, anyway. If you have a player and enemies, and enemies don't collide with each other, it would only grow linearly, since you'd only need to check for collisions between the player and each enemy.


Top
 Profile  
 
PostPosted: Sun Nov 06, 2016 1:43 pm 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1405
Oh, right. I see that your spaceship can shoot six bullets at a time. Yeah, that makes it a bit more CPU-intensive.
I only have up to six checks: Amy with every opponent. And Amy's taser with every opponent.

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Sun Nov 06, 2016 1:47 pm 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7234
Location: Chexbres, VD, Switzerland
dougeff wrote:
Because collision checks increase exponentially as objects increase.

I have 20 objects, but I'm actually doing 80 collision checks per frame. Thus, it is prudent to optimize this function.

In the worst case where everything can collide with everything it's not exponential increase but quadratic increase. We could put a 2-dimentional array showing all object slots and ticking a box when object collide. Adding one more object adds one row and one column, and there's n^2 boxes.

If it was exponential increase, it'd double (or triple, or whathever) the amount of checks for each added object, but here it's not the case obviously.

In practice it's rare to have everything collide with everything, just everything with the player and some type of bullet with the enemies.


Top
 Profile  
 
PostPosted: Sun Nov 06, 2016 4:02 pm 
Offline

Joined: Wed May 19, 2010 6:12 pm
Posts: 2295
DRW wrote:
psycopathicteen wrote:
I never want to design an entire game around using only 4 objects at once. The hardware is already limited as it is, why make it even more limited?

The game wasn't consciously designed around it. It just happened to be this way.
I guess I could easily add another character and still not run into a lag, but the game is already hard enough as it is.
(The game uses auto-scrolling, so you cannot just stop. Most of my testers told me that it's very hard. Adding another opponent would make it even harder.)


At least you're game isn't hardcoded to 4 objects, where it wouldn't spawn a power up if there happens to be 3 enemies onscreen.


Top
 Profile  
 
PostPosted: Sun Nov 06, 2016 4:21 pm 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1405
psycopathicteen wrote:
At least you're game isn't hardcoded to 4 objects, where it wouldn't spawn a power up if there happens to be 3 enemies onscreen.

Actually, it is hardcoded to three enemies.
But it would be a relatively mundane issue to increase the NumberOfCharacters constant. (O.k., some small adjustments would still be necessary, but not many.)

By the way, once per level, Amy's best friend Rachel appears to give her an item and when it's time for Rachel to appear, she won't do so until all the opponents have left the screen.
Because Rachel, her extended arm and the item use the array slots that are usually used for the three opponents.
Although in this case it also has to do with the fact that Rachel and the item have their own color combinations, so we need to remove the opponents before Rachel appears, so that the values in two of the four sprite palettes can be changed and opponents don't spontaneously change colors.

If my game had randomly appearing items throughout the levels, I probably would have given them their own slots in the array. Or I would have used a completely separate array.

But yeah, all in all, NES games written in C can have a good bunch of more opponents, even non-munane ones.
I once heard that "Super Mario Bros. 3" couldn't be done by using C. I haven't tried it out, but I tend to believe this is not the case.

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Sun Nov 06, 2016 5:04 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19116
Location: NE Indiana, USA (NTSC)
psycopathicteen wrote:
At least you're game isn't hardcoded to 4 objects, where it wouldn't spawn a power up if there happens to be 3 enemies onscreen.

Haunted: Halloween '85 has NUM_ACTORS=6 (Donny plus five enemies), with an additional eight-object late entry queue. Health restoration candy dropped after defeating an enemy goes to the entry queue, meaning it may pop in only once an enemy's faint animation completes. And if too many enemies are lured into one place, they can keep other enemies, candy, etc. from spawning. As soon as something is defeated, however, the next thing in the entry queue spawns. This limit is there for two reasons: checking all walkers against terrain is time-consuming, especially when I have to continuously reference terrain in ROM because there's so little RAM, and it helps the level designer avoid situations that might lead to excessive flicker.

Its sequel The Curse of Possum Hollow has the same structure for the most part, but "bullets" (small, lightweight objects) are in a separate pool. And there are more things that can keep things from spawning, mostly if enemies would have more different 1K sprite sheets than MMC3 can handle at once.

As for making Super Mario Bros. 3 in C, you may be right that no existing 6502 compiler would make that practical on the NES. But most Game Boy Advance games were made in C, and I'm inclined to believe that Super Mario Advance 4: Super Mario Bros. 3 was written in C, though I haven't traced through it myself. But given the attitude toward "inefficient programming languages", "HLL code", and "HLL-programmers" expressed in GBATEK, I bet nocash could rattle off a bunch of suboptimal instruction sequences that the C-to-Thumb compilers of the time emitted.


Top
 Profile  
 
PostPosted: Tue Nov 08, 2016 7:29 pm 
Offline

Joined: Fri Mar 22, 2013 7:41 am
Posts: 34
If you use CC65, you give up lots of optimization that can be dome with gcc. If you watch the video a few pages back, you'll see that CC65 couldn't even optimize Hello world.


Top
 Profile  
 
PostPosted: Wed Nov 09, 2016 1:20 am 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1405
FaTony wrote:
If you use CC65, you give up lots of optimization that can be dome with gcc. If you watch the video a few pages back, you'll see that CC65 couldn't even optimize Hello world.

So? Has anybody ever said anything different?

As far as I remember, all I said was that C++-specific features are, for the most part, stuff that shouldn't be used in an NES game. This statement is true regardless of the compiler. Even in gcc for the NES, I would suggest against using stuff like object orientation or templates.

So, yeah, if somebody manages to do a gcc version that can work with NES games, I might use it. But this fact tells nothing about the things that I said about object orientation being ineffective for NES games since there are things that no compiler could ever optimize away. Stuff like the fact that an object's address in a dynamic array is only known at runtime and each member is therefore used with a pointer.

The fact that your most recent post totally misses the point (since it was never an argument about which compiler is better and since my statements about C++ count equally for every compiler) confirms that you really have no idea what you're talking about.

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Wed Nov 09, 2016 3:21 am 
Offline
User avatar

Joined: Mon Jan 03, 2005 10:36 am
Posts: 2963
Location: Tampere, Finland
FaTony wrote:
If you watch the video a few pages back, you'll see that CC65 couldn't even optimize Hello world.

What video? If you're talking about https://www.youtube.com/watch?v=CdJOSfK1ja0, cc65 produced close to optimal code (when optimizations were enabled) for the simple "hello world" of setting the border color on C64.

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


Top
 Profile  
 
PostPosted: Wed Nov 09, 2016 4:08 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5733
Location: Canada
DRW wrote:
Stuff like the fact that an object's address in a dynamic array is only known at runtime and each member is therefore used with a pointer.

I mentioned before, but there are plenty of practical ways to reduce overhead. Here's a simple example:
Code:
const int NUM_OBJECTS = 16;

class Object
{
protected:
   uint8 index;
   
   static uint8 used[NUM_OBJECTS] = {0};
   static uint8 member_data[NUM_OBJECTS];
   
public:
   Object()
   {
      // generate an index (probably would do this differently, just using as example)
      for (uint8 i=0; i<NUM_OBJECTS; ++i)
      {
         if (!used[i])
         {
            used[i] = 1;
            index = i;
            return;
         }
      }
      throw OutOfObjects(); // or handle the error however you think is appropriate
   }
   
   ~Object()
   {
      used[index] = 0;
   }
   
   void do_something(uint8 blah)
   {
      // only non-static overhead is fetching the index, and possibly virtual function calls
      member_data[index] = blah;
   }
};

Depending on how much work you can do with that index in a single function, the overhead doesn't have to be much of an issue at all.

Again, if the class instance is static, the overhead would be optimized away entirely (zero overhead). Classes don't have to be dynamically allocated to be useful, and they don't have to be pointers to be useful either. Static inheritence is still useful and the problem you're making up doesn't apply to it. We could derive from Object in this example, and the only potential overhead added would be a lookup for virtual functions, if and when you need them, and again optimized to zero overhead if it's statically allocated.

Whole program A.K.A. link time optimization was mentioned, but it's also sometimes practical to just build the entire program in a single translation unit. (This doesn't just apply to small games, I worked on a PS3 project using Unreal 3 where we did this.) Lots of NES developers seem to work this way with C or assembly already, but for a different reason than assisting compiler optimizations.

Even if your instances have to by dynamic and passed only by pointer, the overhead in practical terms is occupation of 2 bytes on ZP for the pointer, and constant use of the Y register for indirect access. Even this doesn't have to be a terrible burden if your compiler has a good optimizer. CC65 is atrocious with pointers, but this is not something that has to be an inherent problem with other compilers, or C++, or objects.

Even if the overhead is bad, as always, many choices are a tradeoff, and it depends what you want to do with them. There's tons of stuff that can be useful even if inefficient. You're insisting on a specific solution for a nonspecific problem.

DRW wrote:
I would suggest against using stuff like object orientation or templates.

Why templates? Templates are typically used to trade code space either for better performance, or for smaller source code. Why do you think this is a bad trade? Sometimes it's bad, but space is often not the primary constraint. There's a lot of other features of C++ besides classes and templates too. FaTony mentioned constexpr and it has huuuuuuuuuuge applications for this kind of thing (and it gets mentioned a lot in the OP's video).


Top
 Profile  
 
PostPosted: Wed Nov 09, 2016 4:50 am 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1405
I've seen the video now as well.

The only reason why CC65 created the additional stuff (LDX, TXA) is because the function is the main function which is called int main(). Therefore, those two additional commands are the return value. Of course you wouldn't have this in a function with return type void. Therefore, this argument is already invalid regarding CC65's optimization.
Maybe someone with a YouTube account should tell this guy, so that clueless people like FaTony don't jump to conclusions.

The other stuff about the CC65 not being able to inline functions: So what? If you really have a function that literally only consist of one line anyway, you can simply write it as a macro.

So, I'm still open to a better compiler. But the only thing that this example showed is the ability to inline functions by the compiler. Hm. Alright. But still nothing revolutionary. Most of my functions have more than one line.

rainwarrior wrote:
Code:
const int NUM_OBJECTS = 16;

class Object
{
protected:
   uint8 index;
   
   static uint8 used[NUM_OBJECTS] = {0};
   static uint8 member_data[NUM_OBJECTS];
   
public:
   Object()
   {
      for (uint8 i=0; i<NUM_OBJECTS; ++i)
      {
         if (!used[i])
         {
            used[i] = 1;
            index = i;
            return;
         }
      }
      throw OutOfObjects();
   }
   
   ~Object()
   {
      used[index] = 0;
   }
   
   void do_something(uint8 blah)
   {
      // only non-static overhead is fetching the index, and possibly virtual function calls
      member_data[index] = blah;
   }
};

Wow! A class with only one member that is nothing but an integer for a general array. I'm sure that's really a reason to use object oriented programming. :roll:

Besides, your do_something function will be ineffective. Because unless all your objects of this class are global (which defeats the purpose of the constructor, since you could just assign the values directly in this case), the access to index will be by a pointer: this->index. If you have 20 other functions using index, this will be a whole bunch of indirect accesses.

And even if your class objects are all global (i.e. the adress of each instance of index is known at runtime), then optimizing the indirect access away would mean to copy all of your member functions in ROM, one batch of functions for each object. So, if your class has five functions and you declare five global instances of the class and optimize for direct access of the members, then you now have 25 functions in your ROM code.

So, no, sorry, this example doesn't disprove any of my objections at all.

rainwarrior wrote:
Why templates? Templates are typically used to trade code space either for better performance, or for smaller source code.

Exactly. And you don't have that much ROM space in an NES game.
Besides, what important use could you have for a template in an NES game anyway?

rainwarrior wrote:
There's a lot of other features besides classes and templates. FaTony mentioned constexpr and it has huuuuuuuuuuge applications for this kind of thing (and it gets mentioned a lot in the OP's video).

You mean stuff like that?
Code:
constexpr int multiply (int x, int y)
{
    return x * y;
}
 
const int val = multiply(10, 10);

O.k., it's a neat little feature. Introduced a mere five years ago, after C++ existing since 1979. So, yeah, that's really a feature that we just can't live without. :roll: All of that is surely useful, but seriously, if someone rejects programming a game because he doesn't have that, there's nothing I can do to help him anymore.
Code:
#define multiply(x, y) ((x) * (y))

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Wed Nov 09, 2016 5:16 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5733
Location: Canada
DRW wrote:
Wow! A class with only one member that is nothing but an integer for a general array. I'm sure that's really a reason to use object oriented programming. :roll:

Wow! :mrgreen:

thefox wrote:
What video? If you're talking about https://www.youtube.com/watch?v=CdJOSfK1ja0, cc65 produced close to optimal code (when optimizations were enabled) for the simple "hello world" of setting the border color on C64.

The first example in that video gets optimized by CC65 fine, but there's a second example near the end that doesn't and I think is actually a good demonstration of one kind of thing it can't do that GCC can.


Last edited by rainwarrior on Wed Nov 09, 2016 5:25 am, edited 2 times in total.

Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 100 posts ]  Go to page Previous  1, 2, 3, 4, 5, 6, 7  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: Bing [Bot], Gilbert, za909 and 3 guests


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