How do you make a level?

Just one question: is there any way to measure how long someone holds a button?

Thanks!

You would have to count the time yourself in either frames or milliseconds.

Counting milliseconds could be done like this:

#include <Arduboy2.h>

Arduboy2 arduboy;

void setup()
{
	arduboy.begin();
}

unsigned long millisecondTarget = 0;

void loop()
{
	if(!arduboy.nextFrame())
		return;

	arduboy.pollButtons();
	
	if(arduboy.pressed(A_BUTTON))
	{
		unsigned long now = millis();
		if(millisecondTarget == 0)
		{
			// 1000ms = 1 second
			millisecondTarget = (now + 1000);
		}
		else if(millisecondTarget < now)
		{
			// A button has been held for 1 second
		}
	}
	else if(arduboy.released(A_BUTTON))
	{
		millisecondTarget = 0;
	}

	arduboy.clear();

	arduboy.display();
}

(I think I wrote a timer class once to make this a bit easier,
but I’ll have to track it down tomorrow.)

1 Like

To count button press time, just use a pin change interrupt on the pin to watch for rising and then falling edges, grab the start and stop values of millis() and then subtract them (you’ll have to check and account for overflow). No polling or blocking and it will be very accurate comparatively.

I think that might be a bit beyond @Johnnydb’s current ability.

I’ve had my Arduboy for at least three years and even I’m not sure how to do that.

1 Like

Good point, I didn’t think about experience or current ability. I’m just so used to programming micros by dealing with low level registers and writing my own libraries I sometimes forget not everyone does things the way I do.

2 Likes

@Johnnydb, I finally found time to write a working example, but…

  • It could do with a bit of a tidy up
  • The gravity and jumping are a bit hacky
    • This could be fixed by using fixed points
  • I’m not sure how much of it you’ll understand, I’m using some techniques you probably aren’t used to
  • The way it’s currently implemented results in ‘slippery corners’
    • This is because I’m using ‘cross collision’ (treating the entity as a cross/diamond rather than a square)
    • There’s a way to fix this, but it would probably even be more complicated

If there’s anything you don’t understand, just ask and I’ll explain.
I’ve used lots of comments so hopefully most of the code will already make sense.

If I have time tomorrow I’ll try to draw some diagrams showing how the cross/diamond collision works.
Basically it looks for collisions at the centrepoints of each edge of the entity’s bounding box rather than the corners (which is why you get the ‘smooth corner’ effect).

3 Likes

Thanks! I’ll take a look at it!

1 Like

This is a really good start for you @Johnnydb … the code is exactly what you want and should be easy to extend. (But then we expect nothing less from @Pharap).

1 Like

I feel like someone has already written this code, or maybe it’s only on Gamebuino… all you need to do is have an array for buttons and increment each time it’s held during the frame loop… when it’s released you reset to 0 (or -1), etc… then if you want to do timing/repeat based things you just check if it’s held > amount or held % amount == 0, etc.

Pretty easy to write it yourself and call it write after you call pollButtons or whatever and then build out a few helper utility methods.

The reason the core lib doesn’t do this is it’d waste another 6 bytes of ram that most people probably don’t need - or else we’d probably already include the future.

@Johnnydb, remember: anything you don’t understand or anything you need help with, just ask.

2 Likes

I looked at the code and I think I understand it, but I do have a couple of questions.

First, you use this-> a lot in your code. Does this do anything special?

Second, in map.h you have two functions called getTile that have different int sizes, uint8_t and int16_t.

TileType getTile(uint8_t x, uint8_t y) const
	{
		// If coordinate is out of bounds
		if((x > this->width) || (y > this->height))
		{
			// Return 'void' tile
			return TileType::Void;
		}

		// Calculate array index
		const size_t index = this->getIndex(x, y);

		// Get pointer to tile data
		const TileType * tilePointer = &this->data[index];

		// Read tile data from progmem
		return readTileTypeFromProgmem(tilePointer);
	}

	TileType getTile(int16_t x, int16_t y) const
	{
		// If coordinate is out of bounds
		if((x < 0) || (y < 0) || (x > this->width) || (y > this->height))
		{
			// Return 'void' tile
			return TileType::Void;
		}

		// Calculate array index
		const size_t index = this->getIndex(x, y);

		// Get pointer to tile data
		const TileType * tilePointer = &this->data[index];

		// Read tile data from progmem
		return readTileTypeFromProgmem(tilePointer);
	}

Why do you have two?

Third, what is avr/pgmspace.h? What does it do?

Finally, why didn’t you use arduboy.collide() to track collision instead of the cross collision way? It could prevent the slippery corners. Was there a specific reason?

Thanks for taking the time to write this code! I appreciate it! :grinning:

1 Like

It’s to do with how classes/structs work.
(Classes and structs are basically the same thing in C++, there’s only one technical difference.)

Classes and structs can contain data (called ‘member variables’, or sometimes fields) and they can contain functions.

When you write a function in a class (called a ‘member function’) you can access the member variables through this->.

Technically it’s optional, but I always use it because I think it’s important to show that the variable being accessed is a ‘member variable’, as opposed to a ‘local variable’ or a ‘function parameter’.

(It’s worth noting that some people use ‘struct’ to mean “a class/struct that only has variables and no functions” and ‘class’ to mean “a class/struct that has variables and functions”, but that’s just convention. As I said earlier, as far as C++ is concerned, class and struct mean almost the same thing.)

Originally when I wrote it I had just the uint8_t version,
then when trying to fix some bugs I added the int16_t version.

I’m not sure if that actually contributed to fixing the bug,
but I left both in because I had everything working.

I can check later to see if removing the int16_t version and using just the uint8_t version causes any bugs, but I know for definite that removing the uint8_t version won’t cause any problems.

That’s the header that contains PROGMEM, memcpy_p and other things related to manipulating progmem.

The documentation for it is here:
https://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html

Because it’s not that simple.
Detecting collisions is one problem, but deciding how to react to collisions is another.

If you look at where the player is about to move to and discover that it’s going to collide with something, you then have to decide how to prevent that collision (a process called ‘collision resolution’), which is where the difficulty comes from.

You can’t just not move or your player might end up floating 2 pixels above the ground.
You could try to just put it on the ground but then it might not touch a wall that it’s supposed to, and that won’t help if it’s actually jumping rather than falling.

There’s lots of different ways to resolve collisions.
Some are more correct than others, some have different side effects (like slippery corners).

I wanted something that was quite simple to understand and quite quick to implement, and I discovered the ‘cross’/‘diamond’ technique in a reddit discussion somewhere.
(There wasn’t any code, but I could understand the idea from the explanation.)

Like I say in the comments, there are better ways that avoid the slippery corners, but I’d need more time to research and implement them, and they would probably be harder to understand.

No problem.
I promised I’d have a go and I like to keep my promises.

I don’t think I’ve ever actually written a platformer before so I had to do a bit of reading beforehand.
I didn’t know about the cross technique before so I’ve learnt something too (and if I ever see a platformer with slippery corners then I’ll know what’s going on).

1 Like

Sorry, one more question: how do functions loadMapFromProgmem() and readTileFromProgmem() work?

1 Like

No need to apologise, ask as many questions as necessary.

First I have to explain memcpy_p.
memcpy_p is a function that copies a whole block of bytes from progmem into RAM.

memcpy_p takes three arguments:

  • A pointer to a writable block of memory (RAM)
  • A pointer to a readable block of progmem
  • A size_t (a kind of integer) specifying how many bytes of memory to copy

In this case, the type of the memory being pointed to doesn’t matter,
the function ignores the actual types of the pointers and just copies the bytes directly.
Because of this it can be dangerous to use, so you have to be careful.

(Not dangerous in the sense that it’s going to damage the Arduboy,
but dangerous in the sense that it’s very easy to introduce bugs and errors.)

That’s one of the reasons I hid it behind a function with a better name.

I specifically chose to write readTileTypeFromProgmem to make it clear what was happening without having to expose the potentially confusing details.

Since you’re interested I’ll explain some of the detail.

  • Firstly readTileTypeFromProgmem is given a pointer to some progmem as an argument.
  • Then pgm_read_byte reads the byte at that progmem location.
  • Then static_cast<TileType> converts that byte of data into a TileType.
  • Then the result of that coversion is returned from the function.

Thus the function does exactly what it says on the tin: it reads a TileType from progmem.
(Technically it reads a byte of progmem first and then converts that into a TileType, but the result is the same.)

Part of the reason this is possible is because TileType is specified to be 1 byte in size by the : uint8_t part of its definition.
Its size can be specified manually because it’s a special kind of type called an enumeration.


Enumerations

Originally I wasn’t going to talk about enumerations unless you wanted to know more,
but then I decided they’re pretty important so I think you should know about them.

There are two kinds of enumerations:

  • ‘unscoped enumerations’, which are the more dangerous, more boring kind, specified by enum
  • ‘scoped enumerations’, which are much safer and much more useful, and are specified by enum class (or enum struct, but I’ve never actually seen someone use that)

I won’t talk much about unscoped enumerations (unless you really want to know about them) because they’re not very useful and you shouldn’t use them anyway.

The point of an enumeration type is to introduce a type that can store one of a limited group of values.
For example, TileType is a good example of an enumeration type because you have a limited number of tile types that you want to store.

Some other good examples of scoped enumerations are card suits (spade, heart, club, diamond), compass directions (north, east, south, west) and days of the week (monday, …, sunday).
All of these encompass a finite set of values.

One of the most common uses for them in Arduboy games are for representing game states (titlescreen, gameplay etc).

Underlying Type

Secretly though, all enumerations are actually integers underneath.
Every enumeration type has a thing called an ‘underlying type’,
which is the integer type used to implement the enumeration.

By default the underlying type is int, but the type can be chosen specifically.
On the Arduboy you’ll usually want to specify uint8_t or unsigned char, which is almost the same thing.
This is because int is 2 bytes and uint8_t is 1 byte.

Type Safety

But, scoped enumerations aren’t the same thing as integers because they have some extra ‘compiler magic’ applied to them.

The ‘magic’ is that it’s treated as a new type that’s not implicitly compatible with plain old integers,
which means it has a property called ‘type safety’.

Type safety is a very important programming concept becuase it’s very powerful.
It prevents errors by preventing the programmer from accidentally mixing different kinds of data.
(E.g. integers and tiles, integers and pointers.)

(Type safety is especially important in C++, and C++ provides many tools for maintaining type safety, especially since C++11.)

In the case of a scoped enumeration, it basically means you can do this:

// Correct: TileType::Grass is a kind of TileType
TileType tileType = TileType::Grass;

But you can’t do this:

// Error: an int is not a TileType
TileType tileType = 0;

You don’t get that power from unscoped enumerations (or from using integer types and #defines).
That’s why I say unscoped enumerations are boring and not very useful,
enumerations without type safety are like cake without icing or sandwiches without butter or fillings.

However, as I said earlier, a scoped enumeration is still an integer underneath,
and because C++ believes in giving the programer as much power as possible,
it’s possible to circumvent the type safety and treat TileType like an integer.

That’s why it’s possible to read a byte from progmem and treat it as an enumeration type.
As long as you use a static_cast you can convert any enumeration type to and from its underlying type.

The static_cast is like a kind of waiver or a kind of promise,
it’s like saying “I know what I’m doing and I accept the consequences of doing it”.

Thanks to that power you can do lots of other tricks with enumerations, but that’s a story for another day,
this is already getting longer than I intended.


Hopefully that all makes sense.
If you have any more questions, feel free to ask.

1 Like

Thanks @Pharap!

Two more questions: one, how do pointers work, and two, can I use your code in my game?

1 Like

I saw your comment earlier but didn’t have chance to reply until now.


Sure, but make sure you follow the licence rules.

Apache 2.0 only has two rules that you have to follow:

  • leave the licence notices intact
  • if you modify any of the code then “You must cause any modified files to carry prominent notices stating that You changed the files”
    • which basically just means you have to leave a comment saying // Modified by Johnnydb either near the top of the code or near the bit that you’ve modified

If you want you can even take the code I wrote and build your game on top of it rather than taking pieces of it and trying to integrate them into the code you already have.


Be warned, this is a pretty big topic so there’s going to be a lot of reading.

Pointers

Most kinds of computer memory are what is called “addressable”.
This means that every byte has a numeric address that can be used to access it.
E.g. if a chip had 1024 bytes of RAM you could specify the first byte (byte number 0) by specifying the address 0.

One easy way to think of addresses is to imagine that RAM is a big array of bytes and that an address is an index into that array.

In C++, those addresses are represented by pointers.
So essentially a pointer is actually another kind of integer with some compiler magic added on top.

But for pointers the ‘magic’ is even more important than it is for enums,
because reading or writing memory incorrectly can have serious side effects that could cause your program to crash.

E.g. if you write to the wrong area, overwrite something important or read the wrong bit of memory then your program could end up behaving oddly because it’s encountered a value that it didn’t expect or that it hasn’t been written to handle.

For that reason, it’s incredibly rare to treat a pointer as an integer,
and if done incorrectly it could have some bad side effects.

In general, raw pointers are considered ‘dangerous’,
so they should normally be avoided in favour of other approaches.

However on an embedded system you’re forced to do at least some low level programming,
so you’re more likely to need to use pointers than you would be if programming for a desktop/laptop.

Progmem and EEPROM Pointers

AVR chips behave quite differently to other chips when it comes to memory.
Most modern chips (and even some old ones) have just one address space that covers multiple kind of memory.
But AVR chips have three different address spaces, one for each different type of memory:

  • RAM
  • Flash (progmem)
  • EEPROM

As a result of this, the CPU has different instructions for accessing each address space.
That’s why on the Arduboy you have to use functions/macros like pgm_read_byte and eeprom_read_byte/EEPROM.read.

The pgm_read_xxx, eeprom_read_xxx and eeprom_write_xxx functions/macros specifically require a pointer to work,
but the EEPROM functions can work with just numbers (because as I said earlier, pointers are just integers underneath).

Using Pointers

The reason pointers are better than using integers for accessing RAM is the same reason scoped enums are better than integers: type safety.

Here’s an example of some basic use of a pointer:

int i = 5;

// ip is a pointer to the address of i
int * ip = &i;

i = 8;

// Get the value stored in the variable that ip is pointing to
int a= *ip;

// This prints '8'
arduboy.print(a);

char c = 2;

// Error, the type of &c is char *, not int *
int * cp = &c;

(You’re unlikely to see pointers used like this in real code, this is just an example to show the syntax.)

Adding a * onto the end of any type name makes it a pointer to that type.

E.g.

  • int * is a pointer to an int
  • char * is a pointer to a char
  • int * * is a pointer to a pointer to an int

That means that the syntax for declaring a pointer is _type_ * _identifier_.

The syntax for getting the address of (i.e. getting a pointer to) a variable/object is &_identifier_

(& in this context is the ‘address-of operator’. Not to be confused with the ‘bitwise and’ operator, which uses the same symbol but operates on two values/objects.)

The syntax for dereferencing a pointer (i.e. getting the value that a pointer points to) is *_identifier_.

(* in this context is the ‘indirection operator’. Not to be confused with the ‘multiplication’ operator, which uses the same symbol but operates on two values/objects/)

Null Pointer

There’s a speciall kind of pointer called the ‘null pointer’ which represents a pointer that doesn’t point to anything.

In C, C++98 and C++03, this is represented by the NULL macro.
But in C++11 they introduced nullptr, which is much better because it’s a proper keyword and it has additional safety features.
The Arduino IDE supports C++11, so nullptr is what you should stick to using.

References

I won’t go into detail about these in this comment because it’ll be another page of explanation.
(If you want to know more about them, ask and I’ll explain them in a separate comment.)

Basically these behave a bit like pointers in that they refer to an object,
but they have some important extra rules:

  • A reference can only be assigned once (i.e. they can only ever refer to one object)
  • A reference cannot be null
    • Technically it’s possible on some compilers, but the rules of the standard forbid it, and it’s not something you can do accidentally, you’d have to be trying to do it on purpose

Because of this, references are much safer and thus they are greatly preferred over pointers.

Three more questions @Pharap:

First, my player is 16 by 24, so he looks like he has sunk into the ground, so how do I make him look as if he was standing on the ground?

Second, how can I make an enemy walk on the ground and make contact with the world? I sort of want a Mario style type of enemy (like a Goomba if you know what that is).

Finally, do I have to say Modified by Johnnydb if I build on your code?

Thanks for your explanation of pointers! I never knew how to use them or what they do, but now I do!

1 Like

Hrm, that’s going to complicate things a bit.
Wish I’d thought of that before writing the code instead of making assumptions.

I know half of what needs to be done but I need time to figure out which variables to substitute.

Is the whole of the player still expected to collide with the world?
If so a 16x24 player isn’t going to fit through 1 tile high vertical gaps, it’ll need at least 2 tiles.
Also if the player stands on a high enough tile then the player’s head will be offscreen,
in which case I might need to stop the player colliding with the ceiling.

Alternatively, you could have the player physics work as if the player were 16x16,
but still draw the sprite as 16x24, which would be a lot easier for calculations and movement,
but it might look a bit odd.

It’ll have to use the same collision code as the player does.

Would the enemy be the same size or a different size?

You do, but that’s a good thing - you want to put your mark on it to show that you’ve modified it and thus some of the code is your own code.
You can even put a second copyright notice on it if you want, e.g.

// 
// Modifications Copyright (C) 2019 Johnnydb (@JohnnydCoder)
// 

No problem.

Normally in C++ you don’t deal with raw pointers,
they’re either hidden behind smart pointers or you use references instead,
but on the Arduboy you have to encounter pointers for reading from progmem at least.

Here’s an example of drawing the player as 16x24, but keeping the collision mechanics the same:


I’m half tempted to suggest joining up with you to work on the game together,
but I’m not sure if I have time to work on it properly.

Thanks for the code!

I should have mentioned it instead of having you guess. Sorry. :slightly_smiling_face:

It will be a 16 by 16 size.