Untitled pseudo-3D scrolling platformer

Finally got the foundation for a pseudo-3D game laid out… still trying to decide which direction to take it in, not sure if i want to try for something like 3D World Runner, Mechwarrior, or maybe Battlezone since each one has their upsides and downsides when it comes to implementation… i also need to refactor the code to clean it up a bit (so i havent made a repo yet) but here it is for now:

scrollingShooterTest.ino.hex (27.7 KB)

#include <Arduboy2.h>

Arduboy2 arduboy;

const int8_t horizonY = 10;
const uint8_t numLines = 6;
const uint8_t numEnemies = 3;

const uint8_t baseEnemySize = 4;
const uint8_t enemySizeRange = 8;

float lineSpacing[numLines];
float enemyPosX[numEnemies];
uint8_t enemyLine[numEnemies];

// Setup function initializes the Arduboy, sets the frame rate, and assigns random initial positions for the enemies.
void setup() {
  arduboy.begin();
  arduboy.setFrameRate(30);
  arduboy.initRandomSeed();

  // Calculate the initial Y positions for each line.
  for (uint8_t i = 0; i < numLines; ++i) {
    lineSpacing[i] = (arduboy.height() - horizonY) * (i + 1) / (numLines + 1) + horizonY;
  }

  // Set random initial positions for the enemies.
  for (uint8_t i = 0; i < numEnemies; ++i) {
    enemyPosX[i] = random(arduboy.width());
    enemyLine[i] = random(numLines);
  }
}

// The loop function handles input, updates the game state, and renders the screen.
void loop() {
  // Skip the rest of the loop if it's not time to draw the next frame.
  if (!arduboy.nextFrame()) {
    return;
  }

  // Poll for button presses.
  arduboy.pollButtons();

  // Clear the screen buffer.
  arduboy.clear();

  // Move lines up or down based on user input.
  if (arduboy.pressed(UP_BUTTON)) {
    for (uint8_t i = 0; i < numLines; ++i) {
      lineSpacing[i] -= 1;
      // Wrap the lines around if they go above the horizon.
      if (lineSpacing[i] <= horizonY) {
        int8_t previousLine = (i + numLines - 1) % numLines;
        lineSpacing[i] = lineSpacing[previousLine] + ((arduboy.height() - horizonY) / numLines);
      }
    }
  } else if (arduboy.pressed(DOWN_BUTTON)) {
    for (uint8_t i = 0; i < numLines; ++i) {
      lineSpacing[i] += 1;
      // Wrap the lines around if they go below the bottom of the screen.
      if (lineSpacing[i] >= arduboy.height()) {
        int8_t nextLine = (i + 1) % numLines;
        lineSpacing[i] = lineSpacing[nextLine] - ((arduboy.height() - horizonY) / numLines);
      }
    }
  }

  // Move enemies left or right based on user input.
  if (arduboy.pressed(LEFT_BUTTON)) {
    for (uint8_t i = 0; i < numEnemies; ++i) {
      enemyPosX[i] -= 1;
    }
  } else if (arduboy.pressed(RIGHT_BUTTON)) {
    for (uint8_t i = 0; i < numEnemies; ++i) {
      enemyPosX[i] += 1;
    }
  }

  // Draw the horizon line.
  arduboy.drawLine(0, horizonY, arduboy.width() - 1, horizonY, WHITE);

  // Draw the lines representing the ground.
  for (uint8_t i = 0; i < numLines; ++i) {
    // Calculate the current Y position for each line based on its spacing.
    int8_t currentY = (arduboy.height() - horizonY) * pow((lineSpacing[i] - horizonY) / (arduboy.height() - horizonY), 2) + horizonY;
    // Draw the line at the calculated Y position.
    arduboy.drawLine(0, currentY, arduboy.width() - 1, currentY, WHITE);
  }

  // Draw the enemies.
  for (uint8_t i = 0; i < numEnemies; ++i) {
    // Calculate the enemy's Y position based on its assigned line.
    float enemyPosY = (arduboy.height() - horizonY) * pow((lineSpacing[enemyLine[i]] - horizonY) / (arduboy.height() - horizonY), 2) + horizonY;
    // Calculate scaling factor for the enemy based on its Y position.
    float scaleFactor = (enemyPosY - horizonY) / (arduboy.height() - horizonY);

    // Calculate the enemy's width and height based on the scaling factor and the size variables.
    uint8_t enemyWidth = baseEnemySize + enemySizeRange * scaleFactor;
    uint8_t enemyHeight = baseEnemySize + enemySizeRange * scaleFactor;

    // Draw the enemy as a rectangle with the calculated width and height.
    arduboy.drawRect(enemyPosX[i] - enemyWidth / 2, enemyPosY - enemyHeight / 2, enemyWidth, enemyHeight, WHITE);

    // Wrap the enemy's X position around the screen boundaries.
    if (enemyPosX[i] < -enemyWidth / 2) {
      enemyPosX[i] = arduboy.width() - enemyWidth / 2;
    }
    if (enemyPosX[i] > arduboy.width() + enemyWidth / 2) {
      enemyPosX[i] = -enemyWidth / 2;
    }
  }

  arduboy.display();
}
1 Like

Looks like Juno First.

None of the code looks the same though.

1 Like

I love that game :grin: and thats likely due to how ive been developing it (since its been interative)… first i had to get the line spacing right, then it took a bit to get the scrolling to work (so the spacing updated dynamically as it scrolled), then wrapping, scaling, etc… but the wrapping and scaling was more just playing around with it (since i suspect sprite scaling would be a waste of ram vs just changing sprites). If i decide to go with something more like 3D World Runner then wrapping and scaling might come in handy, but if i try to make it something with a more open map then ill need to restructure it a bit so that everything is in a proper 2D coordinate system (and displays accordingly).

How much of this was AI generated?

Almost all of it, but definitely not all at once, it was iterative… basically i knew how to create and draw the lines but couldnt igure out what sort of math i would need for the spacing (which took a little while to get right), so naturally i couldnt figure out how to make them dynamically change the spacing between them as they scrolled either (then that took a little while to get right)… i probably could’ve figured out the scaling on my own (since that was the idea i initially had in my head when kicking ideas around) but i asked it to add that just to save time because i wasnt sure if id even use it… and when i asked it to refactor this code it separated everything into its own functions (and made it much better organized).

The up and down buttons seem to be reversed. When I press up, I am expecting the lines to move towards me. Also, I wouldn’t expect to be able to move left / right unless I am also moving forward!

But you probably know all this!

Yeah, i meant to swap the directional buttons, just hadnt gotten around to it yet lol. As for moving left and right, i just did that to play around with wrapping more than anything (since everything only exists in screen coordinates at the moment anyway) but i was wondering how i might be able to reconcile world coordinates with a camera FOV so that actually helps to consider that, thanks!

Yep, I understand its a work in progress … heaps of potential here for a great game!

Maybe something else to look at is the inertia of the player. Glide to a stop rather than the hard stop when you release the keys.

1 Like

That’s both disappointing and completely unsurprising.
There’s quite a few inefficiencies in there, but I’ll leave the AI to clear those up for you.

I am not sure what you are expecting. Of course its only as good as the documents it is trained on but there is a possibility that its skips some less common techniques in favour of well represented ones (read more source samples). If the intent is to provide good sample code then that’s not a bad thing.

Ah, i see that how you added that effect in Juno First and i like that! I suppose that just depends what sort of game im able to turn it into… im thinking something like 3D World Runner would probably be the easier choice (since i could store levels/sprites/etc on the FX and would only need to worry about movement in one direction) whereas trying to figure out a world coordinate system and the appropriate movevemt/camera stuff for the other two is probably outside of my scope… but if i am.able to figure it out then ill definitely keep that in mind, thanks!

Lol yeah, im not exactly surprised… when i looked at it again after you said that i realized the horizon was changed to an int8_t at some point… I’ve been trying to ensure the correct data types are being used at each step of the way (in this code, at least… didnt even bother looking at the other stuff i posted) and it actually has been using the correct data types about 75% of the time, but it should also be worth noting that the quality of responses tends to degrade over the course of a dialogue (since its only a static model and cant actually remember or plan for anything)… to the point where it will be using the correct data types throughout the conversation then all of a sudden will start trying to introduce a bunch of ints in there (while the rest of the variables in the code are still int8_t, uint8_t, etc)… over the course of it i got a bit more focused on other functionality and overlooked that (and im sure other things i dont understand).

Still though, i completely failed all of my clases last semester (even though i went to office hours every week, i had one teacher for all four of my classes and he could only offer so much help, but thats beyond the point)… so i had to get an extension on my classes, finished the semester late, and had to take this semester off since i wouldnt finish in time (and will be taking the summer off)… so this is still helping to refresh my memory on a lot of stuff and keeping me from forgetting (and having to relearn it) for literally the 5th time now lol. For the last few years ive been seriously questuoning whether or not im even able to learn anymore at all or if my brain is just unable to retain any new information as i get older (and as my mental health has gotten progressively worse- not like how i feel but cognition, etc)… i used to think people could learn whatever they wanted (and it just took finding how they learned best)… not anymore though :laughing:

Yes, a form of the mediocrity principle.

That’s kind of my point though. It regurgitates commonly represented patterns without any actual ‘thinking’ involved. It doesn’t notice the things that a proper programmer would. It picks up techniques and their frequences and a bit of their context, but doesn’t really ‘understand’ them or make critical decisions in the way a human would.

Hence merely reading code generated by a chatbot is a poor way to learn compared to reading tutorials written by actual humans and actually understanding the problem that the code sets out to solve.


I wasn’t even thinking of that. That’s pretty minor compared to the other stuff.

For example, things like:

  • Using pow(x, 2) when x * x would likely be much cheaper.
  • Using arduboy.drawLine(x, y, w, y) when arduboy.drawFastHLine(x, y, w) would be more appropriate.

That’s unfortunate.

Did you consider asking for help here at all?
Naturally nobody can do your work for you, but we likely could have helped with explaining some of the topic material.

Higher level education tends to be very ‘hands off’. (Though it does depend on the teacher.)

I once had a teacher at college who taught me literally nothing. On the first day he rambled on about buses and bridges, and then every other day he just made a note of who was and wasn’t at the lesson and didn’t actually teach anything, he just sat at his desk and didn’t interact.

I suspect reading some existing (and more comprehensive) Arduboy game code and doing a bit of manual practice would be more effective. Nobody gets good at any skill without practice - you have to ‘use it or lose it’.

“Cognitive health”.

It depends how old ‘older’ is. Forgetfulness is a symptom of aging, but there’s a difference between natural forgetfulness and actual memory problems.

When I was doing psychology in college (which feels like a lifetime ago now), amnesia was one of the topics we touched on - both retrograde and anterograde.

If you do think your memory or information processing is somehow atypical or degrading, you should definitely seek a proper examination.

True, but thats why it takes a programmer to know whsts correct and whats not- i never claimed it wa the end-all for functioning code. It is helping me learn though, to the extent that ive been refactoring my code as i go and saving bytes of RAM by reformatting things in a way that i otherwie wouldnt have… like sure i know about the different method its been helping me employ but it wouldve taken me absolutely forever to try them all out myself.

Ive been having trouble understanding which would be more appropriate myself (particularly drawRect or drawFastHLine)

I mainly failed because of this database management class… i was actually excited to take it since i expected it would be useful later on, but it was just learning all of the commands without ever actually using them… then i wasted too much time getting hung up in that class and ran out of time to finish work in my other classes as well.

I suspect it mostly has to do with the fact that all i do is work, never leave my house or see anyone (been living somewhere 3 years and still dont know anyone here) so i spend all of my free time burning myself out on projects i never get anywhere on. That sort of lack of stimulation would burn anyone out after so long.

Also, does Arduboy support the std vector class? I need to create an array but i need to dynamically set the size at runtime and im struggling to figure out how i would do that.

It can support any library I think. At least ones that are purely software and don’t interact with any hardware.

I’ll answer the other stuff later, but…

No. It doesn’t have access to any of the C++ standard library.

It would be a really bad idea anyway, std::vector relies on dynamic allocation which is not a good thing to be doing when there’s 2560 bytes of memory total, and a good 1024 of that is used for the frame buffer, leaving a little under 1536 for game variables.

Using dynamic allocation basically means:

  • The memory could run out at runtime
    • I’m not certain what would happen when it did, I’m guessing the Arduboy would just reset
  • RAM wasted for the memory allocation overhead
  • Progmem wasted for the allocation function

(But at least there’s no CPU cache to worry about.)

Figure out the worst case scenario, set aside a fixed size array that’s as big as you could possibly need, use that.

You can track the number of active elements in the array, increasing and decreasing it as you add elements to the end, and the next element to be occupied always ends up going in the index that matches the current number of occupied elements.

In other words:

bool add(T element)
{
  if(count >= capacity)
    return false;

  array[count] = element;
  ++count;
  return true;
}

Where array is the fixed-size array, T is the type of elements in the array, capacity is the size of the array, and count is the number of occupied elements in the array.

Somewhere I’ve got this entire thing precoded as a templated class, but I can’t dig it out right this minute because I’ve got to head off out.

You use drawFastHLine when you only want to draw a horizontal line and you either need it to be fast or you aren’t using drawLine, drawRect, or fillRect extensively elsewhere.

As is often the case in programming, you have to choose between speed and memory usage (in this case progmem usage - i.e. program size). drawFastHLine is faster than drawLine, drawRect, or fillRect, but if you need to conserve progmem and you’re using drawLine, drawRect, or fillRect elsewhere then it may be more economical to use one of those, even at the expense of burning more CPU cycles (since on the Arduboy memory is usually more scarce than speed).


Theoretically it could, but as explained above, it would be a bad idea to attempt it.

Interacting with hardware isn’t an issue if the Arduboy can match the capability of the hardware, but doing so would require modifying the library. (Which is one of the reasons open source is useful - open source licences give you permission to do that.)

1 Like

Yeahh, i knew the memory could run out, i just wasnt sure if you could delete after using them (because aside from dynamically allocating with vectors the only other option seemed to be a new pointer then deleting after initialization)

But that makes some sense, ill just have to think on implementation a bit, thanks!

With dynamic allocation, yes, you can delete/free the memory.

With std::vector, it depends on the implementation. Most std::vector implementations just grow indefinitely and don’t make any attempt to ‘shrink’ the allocated memory block unless you explicitly call shrink_to_fit().

That’s more or less what a std::vector does internally, but more dangerous (as bare new and delete or malloc and free always are).

If you were doing that sort of thing on desktop it would be safer to use a std::unique_ptr or std::shared_ptr, with the T template parameter set to an array of unspecified size, e.g. std::unique_ptr<int[]> via make_unique<int[]>(size) as of C++14 or std::unique_ptr<int[]>(new int[size]) prior to C++14.

But whether you actually need to do that sort of thing depends on what you actually need the array for, there may be another option.

Found it:

It’s pretty bare bones, and I could probably design a better one now, but it probably works.
I can’t remember who I wrote it for, but I likely threw it together relatively quickly.
If you use it and find any bugs, let me know and I’ll fix them


Though you don’t necessarily learn why those changes save memory, or in what context.

I had a unit a bit like that. It’s was pretty much all theory and entity relationship diagrams with minimal SQL, and of course horribly dull.

I can’t really remember much other than an argument over how to pronounce tuple and the teacher being a bit dim: I handed in a draft essay, she gave it back with a few complaints and annotations, I made no alterations to it whatsoever yet the grade I got for the final version was magically better than the grade she gave the draft.

Ditto. There’s nothing worth venturing outside for where I live.

I have projects that I work on, but I don’t burn myself out on them.

If I never complete something that’s fine by me, as long as I learn something along the way or enjoy the time spent on the project. Self-enrichment trumps achievement as far as I’m concerned.
Life is a marathon, not a sprint.

(I was going to say software development is not a sprint either, but the agile methodology people would probably come after me with pitchforks.)

Nevemind, i have to redo all of this for the millionth time anyway :expressionless: ill just have to reimplement it and go about it a different way.

Well in that case it had to do with encapsulation, and reusing a helper function instead of having two separate ones that functionally achieved the same thing.

Yeahh, i just get tired of never making any progress, and having nothing to show for all of my time and energy over the years. Not even knowledge, considering any time i try to make something im back at square one, then i always have to redo the same thing over again no matter what i do (like this for example, ive spent two weeks on it and literally havent progressed at all, just had to keep redoing the same 2 things about a dozen times for one reason or another) :expressionless: but im just burnt out in general, cant do much about that i suppose. Also makes me really second guess why im even going to school or whether or not i can manage a job after i get out :laughing:

Because you don’t have enough RAM, or because you can’t figure out how many you’d need in advance?

If you don’t have enough RAM then using dynamic allocation wouldn’t really help either.

There are other potential tricks you could use to save memory, but it really depends on what you actually want the array for.

If you’re not using the array for rendering and it doesn’t have to exist between frames then you could potentially borrow the frame buffer as a large pool of memory (1KB) for use.

Otherwise you could statically allocate a pool of memory and use it for different purposes at different points in your program, either via a union or by statically allocating an array of char/unsigned char and doing some magic with placement new or reinterpret_cast.

“Encapsulation”? You mean the AI chose to stick something in a struct?

Like many optimisations, whether that actually helps or not depends on the circumstances.

If it did actually help then I’d presume the reason it helped is because one or both functions were large enough that not being inlined used less memory than inlining the function.

For a sufficiently small function (e.g. min, max) that likely wouldn’t have made a difference.

But then again, it depends on circumstance and what the compiler decides to do (and why).


Which two things? And for what reasons?

As I said before, 3D rendering isn’t particularly easy. You’d do well to leave copious comments in your code or to write down an explanation of how the rendering works and why and refer back to it. (You may well be better off writing it down in physical paper rather than as a digital document.)

If things are that bad you really ought to seek medical advice from a doctor or psychiatrist. Issues with retaining information could be caused by anything from anxiety to taking antidepressants to needing reading glasses to just having too many distractions (especially phones and social media), but whatever the cause you’re best off seeking a professional opinion.