tokumaru wrote:
and you often have to tailor your C code in not necessarily intuitive ways in order to produce ASM code that's not slow or buggy, so you end up not only having to learn how ASM works, but you also have to study a whole lot about the C->ASM process and the pre-made libraries if you expect to make anything beyond trivial.
That's nonsense. Yes, there are a few things to keep in mind, like using global variables instead of locals or parameters or creating an array of a struct as a struct with arrays. But it's not like your C code would look totally outlandish in the end.
I programmed a whole jump'n'run and my code still looks pretty clean and C-like.
Writing C can still be a ton easier than writing Assembly directly.
If you can program Assembly well enough, that's of course always better.
But someone who can write fluently in C, but only very slowly in Assembly:
Don't tell those people that the adjustments that you have to make in the C code to make it efficient enough for NES programming are such a huge disadvantage and such a code uglyfier that they're better off writing Assembly directly. Because that's simply not true.
Don't tell people that they can only either program clean Assembly code or butt-ugly, unintuitive, unreadable C code that's so shitty and energy-taking that they have less trouble using Assembly right away. Because this is clearly nonsense.
For someone who is good in C, but slow in Assembly, C is still a huge help for NES programming. And no, not only for "Pong" or "Space Invaders". It has been proven several times that you can make a real game for the NES by using mostly C:
http://www.youtube.com/watch?v=Eee0yurkIW4Even if you need to use Assembly for the really time-critical stuff or for understanding what's going on under the hood: There's still a difference whether you force yourself to write an UpdateSprite or ProcessNmi function in Assembly or if you have to code the whole game logic and simply
eveything in Assembly.
In the following, I will present you my movement function for the most basic opponent of my game. Please tell me:
Is the function so ugly and un-C-like and tailored in unintuitive ways that you would usually never use in C, so that I would have been better off writing it in Assembly directly? Or is this pretty much regular C for the most part, so that somebody who knows C but can program in Assembly only very slowly really has an advantage in using C?
Let's see if my function will convince a C coder to switch to Assembly because he considers the code to be such an abomination that he sees no use in programming C on the NES and prefers to become a master in Assembly programming instead because he considers this the easier way.
Code:
/* The most basic opponent who
just walks left and right. */
void __fastcall__ MoveGoon(void)
{
/* If the Goon is stunned, */
if (Npcs.IsStunned[IndexChr])
{
/* we decrement the stunned counter.
The Goon cannot escape from the stun.
The counter is just there to make
the vibrating movement in "Level.c". */
--Chrs.StunnedCounter[IndexChr];
/* If he's stunned,
nothing else is done. */
return;
}
/* Often used array variables are put
into temporary variables because
accessing them is smaller and faster. */
MoveGoonX = Chrs.X[IndexChr];
MoveGoonFramesForNextWalkingAnimation =
Npcs.GoonFramesForNextWalkingAnimation[IndexChr];
/* If it is declared that
the Goon leaves the screen,
then nothing can stop him. */
if (Npcs.GoonMovesOutOfScreen[IndexChr])
MoveGoonX -= GoonMovementPixelsLeft;
/* Movement to the right and
to the left are done differently.
We first check for right. */
else if (Chrs.Direction[IndexChr] == DirectionRight)
{
/* We calculate the new position
of the right border side.
We use CharacterFullBorderRight
because the Goon's drawn size is
bigger than his bounding boxes. */
MoveGoonNewPositionRight =
MoveGoonX
+ (CharacterFullBorderRight + GoonMovementPixelsRight);
/* If the new position is outside the screen or over a gap, */
if (MoveGoonNewPositionRight > 255
|| GetOnScreenLevelDataValue(MoveGoonNewPositionRight) == 0)
{
/* then we change the direction. */
Chrs.Direction[IndexChr] = DirectionLeft;
/* We reset the frames for the next
walking animation since left and
right have different values. */
MoveGoonFramesForNextWalkingAnimation =
GoonFramesPerWalkingAnimationLeft;
/* And we immediately let the Goon
walk the new distance. */
MoveGoonX -= GoonMovementPixelsLeft;
}
/* If the Goon doesn't reach
a border, we just let him
continue walking. */
else
MoveGoonX += GoonMovementPixelsRight;
}
/* Movement to the left. */
else
{
/* We check the new left border position. */
MoveGoonNewPositionLeft =
MoveGoonX
+ (CharacterFullBorderLeft - GoonMovementPixelsLeft);
/* And we check for a position a bit
to the right of the Goon.
This way we check whether the room
where movement is possible for
him has become very small.
That room is exactly 2/3
of CharacterWidth. */
MoveGoonNewPositionRight =
MoveGoonX
+ (CharacterFullBorderLeft + 3 * CharacterWidth / 2);
/* If that certain right position is not a gap
and the Goon's new left position would be
either outside the screen or be a gap, */
if (GetOnScreenLevelDataValue(MoveGoonNewPositionRight) != 0
&& (MoveGoonNewPositionLeft < 0
|| GetOnScreenLevelDataValue(MoveGoonNewPositionLeft) == 0))
{
/* then we switch the Goon's direction. */
Chrs.Direction[IndexChr] = DirectionRight;
/* Just like in the code above,
the walking animation frames
are reset. */
MoveGoonFramesForNextWalkingAnimation =
GoonFramesPerWalkingAnimationRight;
/* And the Goon is moved. */
MoveGoonX += GoonMovementPixelsRight;
}
else
{
/* If the Goon's new left position
would be outside the screen
and that specific right position
is a gap, i.e. if the room
where the Goon can move
has become very small, */
if (MoveGoonNewPositionLeft < 0
&& GetOnScreenLevelDataValue(MoveGoonNewPositionRight) == 0)
{
/* then leaving the screen is a given. */
Npcs.GoonMovesOutOfScreen[IndexChr] = true;
}
/* The movement is done. */
MoveGoonX -= GoonMovementPixelsLeft;
}
}
/* The movement animation is done.
If the frame counter is at zero, */
if (--MoveGoonFramesForNextWalkingAnimation == 0)
{
/* we get the next animation
phase from the array
and apply it to the meta sprite. */
Chrs.MetaSprite[IndexChr] =
GoonWalkingMetaSprites
[
Npcs.GoonWalkingIndex[IndexChr] =
(Npcs.GoonWalkingIndex[IndexChr] + 1) & BitMask(GoonWalkingMetaSprites)
];
/* And we reset the counter,
depending on the direction. */
MoveGoonFramesForNextWalkingAnimation =
Chrs.Direction[IndexChr] == DirectionRight
? GoonFramesPerWalkingAnimationRight
: GoonFramesPerWalkingAnimationLeft;
}
/* The temporary variables
are put back into their
original arrays. */
Chrs.X[IndexChr] = MoveGoonX;
Npcs.GoonFramesForNextWalkingAnimation[IndexChr] =
MoveGoonFramesForNextWalkingAnimation;
}