Vol. Sep
Thank you very much! どうもありがとうございました!
8
2017
Contents:
Street art in Shinjuku, Tokyo
PLAY
New games out this month! Community Polls - Arduventure buttons The Importance of Comfortable Screen Space Utilization with crait
LEARN
Design & Development with filmote - Pipes (Part 2)
Game elements with filmote - Collisions (Part 2) Pixel page with Mainy1009 - The Style Pile
EXPLORE
Tokyo Maker Faire 2017 Questions with Game Developer - inajob Customized Arduboys #arduboy
Page 2 of 53
A welcome note:
GODZILA! RUN! But it’s okay.. We managed to keep the Arduboys safe and had a great Maker Faire Tokyo! Thank you all who dropped by and said hello - from the Kickstarter backers, to anyone who just happened to walk by our booth. At the faire we also had Ivan helping us to communicate with the vistors in Japanese. This visit also made us realise more then ever how important localisation (at least / even in some small step) is. Japanese friends, don’t forget our community pages: https://community. arduboy.com/c/ja :) -celinebins
Do you wish to contribute? Have an interesting article you wish to share with the Arduboy community? A cool project you did, your experience on working on it? A funny joke? We want to hear it! Send arduboymag a message through the Arduboy community page or a DM on Twitter! Page 3 of 53
Thank you to this months contributors! @crait: Creator of various Arduboy games such as Circuit Dude and Train Dodge, crait has written tutorials on how to get started with creating your own game, and developed Arduboy Manager; an easy way to upload games to your Arduboy without using the Arduino IDE. http://www.crait.net/
@filmote: In between writing articles for this magazine, Simon is writing an Arduboy version of the classic 1943: The Battle of Midway game. He hopes to release the game this September.”
@JO3RI: Better known as TEAM a.r.g, some of Arduboy’s community favorite games are written by them, as well as their own Arduboy Game Loader, where you can easily upload your favorite Team a.r.g games without using the Arduino IDE. http://www.team-arg.com/
@Mainy1009: Not sure how to make sprites for your game? Read about pixel art creation this month! Follow this pixel artist on Twitter at: https://twitter.com/Mainy1009
@MLXXXp: The biggest contributor to the Arduboy Arduino Libraries also contributes to the magazine with small but useful tips and tricks - look for them with the light bulb icon!
@TheArduinoGuy: Author of the ArduBoy games - Jet Pac & Arcodia.. Author of ‘Beginning Arduino’. Medway Makers. Maidstone Hackspace. http://thearduinoguy.org/
@Your name: This could be you.. Help contribute to the Arduboy Magazine!
Page 4 of 53
Arduboy
Company
news:
What’s happening at headquarters?
@Celinebins
Upcoming events October
The Arduboy team will be taking part in Game Start Asia (14-15 Oct), and Portland Retro Gaming Expo (20 - 22 Oct). Drop by and say hello!
New Distributor
We are happy to annouce a new distributor in Singapore - TOYTAG! If you live in Singapore this is the perfect chance to get one locally without paying for shipping. Visit their store at Harbourfront centre or buy one online.
Where to buy Arduboy? In order to better deliver the Arduboy to you, a distribution model for sales has been currently implemented. You can now purchase the Arduboy from a variety of different retailers from around the world!
New!
Talk to us!
Page 5 of 53
W
ES AM G
by @Celinebins So many new games were released this month! If you missed them on the community pages, here they are.
FR ES H
N E
PLAY:
That will get you hooked
cosmic
Pods
@cubic9com A tiny shoot’em up game where as a cosmic squid, you shoot enemy octopuses to stay alive!
The Guy who never complains
@JuiceLizard Arduboy’s 1st ebook! Read 32 small and funny chapters by flipping pages with the A and B buttons.
arducross
@oldmouse Very early Picross clone.. Currently only has 3 boards, a 5x5, a 10x10, and a 15x15. Download the WIP and give feedback to help!
Page 6 of 53
Up coming events ! Join the Arduboy team at these events in October! Come say hi to us, try out some demo units (if you do not have an Arduboy yet), ask us questions, and enjoy special event discounts. ;)
Game start asia Suntec city Singapore Southeast Asia’s Premier Game Convention is back for its 4th edition on 14 & 15 October 2017! This year is set to bring back annual favourites as well as new and exciting content for everyone, including game showcases and previews, esports, indie games, retro gaming, cosplay and more!
Get tickets here: https://gamestart.asia/
Page 7 of 53
Community POLLs! by @Celinebins
With the completion of Arduventure nearing, a speci “Arduventure Edition” would be made. This month we t community pages and asked everyone if they prefer GOLD buttons on the custom Arduventure Ardub These are the results!
66%
Gold
buttons!
Bling
Bling!
“I’m on Team Gold. It makes me think of the early NES Zelda carts” - @jrronimo
More
info
The back will be anodized black, the etching will remove the dye and so it will be aluminum colored, or actually a bit white. The metallic parts all stay the same.
Ardventure edition
special
Arduventure is the most anticipated Arduboy RPG game yet to be released. Get excited! Listen to the chiptune intro music here!
ial black took to the BLACK or boy..
34%
Images are simulated in photoshop, nothing exists yet.
black
buttons!
Stealth
mode!
“I like the gold ones because they are gold, but I voted for the black ones because the arduboy itself look badass. It is like my mind say I should like the gold ones, but my heart like the stealth one.” -@eried
Others? “White buttons - Why? It matches the monochrome screen.” - @pharap “How about using clear plastic so the gold domes of the actual buttons show through?” - @MLXXXp
Thank you everyone for being part of this discussion! Reply to the thread if you are excited for Arduventure! Page 9 of 53
Importance of Comfortable Screen Space Utilization by @crait There’s a lot of very beautiful games for the Arduboy. One of the most beautiful, in my opinion, is TEAM a.r.g.’s game, Sirène, by JO3RI with artwork by Justin Cyr. The graphics are very detailed and the animations are smooth. If you’ve never played it, I highly recommend it. Even though the graphics are detailed and complex, I do not feel overwhelmed when looking at the screen. It’s comfortable and I can see what is going on. The background pieces are not distracting and the sprites feel like they are perfect size for the screen.
elements in the game and how you show UI elements. (UI stands for User Interface and it generally describes the numbers, menus, pop-ups, and other graphical assets like player score or health.) When designing a game, you want these elements to be out of the way of the main action. If you have too many that are in the way, the action becomes hard to follow. For this reason, in-game UI elements are typically placed in the corner of the screen. Some games refer to these as HUD’s in first-person shooters. Check out the layout of Halo’s HUD (below).
Comfortability can make a huge difference in the outcome of a game. You could have an awesome game designed and programmed, but if it’s impossible to distinguish what’s going on in the screen, the game could be unplayable. This is why comfortability is so important. There are A LOT of factors that play into how a gamer perceived a game when playing, but 3 components of comfortability that I want to talk about, that I think are most important to Arduboy and mobile game development, are Layout, Aspect Ratio, and Size.
I tried to emulate this style in my game, Roo Run, for the Arduboy.
Of these three components, I think layout is the most straight-forward and easiest to understand. Layout is how you position
The next thing I want to talk about is aspect ratios. ‘Aspect ratio’ is a term people use to describe the relationship
For now, I don’t want to jump too much into the specifics of laying out a game, but I do want to at least mention that the most important information to learn about the layout is that elements are out of the way of the action. I’ll bring this up in a moment.
to be continued next page >> Page 10 of 53
between the width of a screen and the height of a screen. For instance, most televisions have 16:9 aspect ratio where the width of the screen is 16 units wide and the height is 9 units wide. Many phones have vertical screens, where the width is smaller than the height. My LG G5 actually has a 9:16 display. When people really start working on the Arduboy, they’re sometimes surprised to find out that the size of the Arduboy screen is somewhat unique! It’s screensize is 2:1, which would make it fall under the category of ‘ultra-wide’ screen. It’s kinda funny to think about it this way considering the Arduboy is as small as a credit card, but it’s true. Here’s some more examples of systems and their screen sizes. Ultra-Wide Screens that are 2:1 or bigger: - Arduboy has a 2:1 screen - Letterbox films run at 2.35:1 - Hyundai Super TL3
Designing games for the Arduboy can be a little challenging because of it’s lesspopular aspect ratio depending on the kind of game that you want to make. Sometimes, developers don’t even realize that the aspect ratio really does affect what they’re working on! My mother and grandmother LOVE PacMan. They used to play all the time, before I was born. Arcade machines typically have tall screens and knowing this, the developers behind Pac-Man designed maze-like levels that allowed the player to warp from one side of the screen to the other, in order to give the feeling that the playing screen was bigger than it actually was. Because there was a lot of extra space on the top and the bottom, while the sides of the screen were so limited, the score, life count, and other UI elements were placed on the top and bottom of the screen. (This is very common for vertical-screen arcade machine games.)
Wide Screens: - HD TV’s and game consoles like the PS4 run at 16:9 - Gameboy Advance has a 3:2 screen ‘Square’ Screens with close to 1:1 ratio: - Gameboy/Gameboy Color - Gamebuino - Palm Pilot (OS 4.0) - Traditional Computers and TV’s Tall Screens: - Tetris Microcard has a 1:2 screen - Nokia N-Gage - Many classic arcade machines - Modern smart phones
My mother and grandmother were really excited to buy Pac-Man for the Atari 2600. The Atari 2600 played on the conventional television, which had a ratio closer to 4:3, which was similar to a square, but a to be continued next page >> Page 11 of 53
little wider than it was tall. Because of this and other limitations, the levels had to be completely resigned and simplified. If you look at a screenshot, the level actually has the warp moved to the top and bottom of the screen, in order to make the player movement feel like it has more vertical playing space. This complete redesign ended up hurting Pac-Man’s reputationA lot of people hated this version of the game, including my mother and grandmother. As a kid, I wondered why they disliked it so much. It just didn’t have the same charm. The game felt squished, and smaller, and less comfortable to play. Another note I want to make is that the score and lives are STILL at the bottom of the screen, actually taking away valuable playing space from the height of the game. This used to be out of the way since there was a lot of extra space at the top and bottom, but now, it’s taking valuable retail space from the game.
Just because you’ve moved graphical components to the corner of the screen out of the way of the player, doesn’t meant that you’ve moved them to the right location. Coming up with the proper layout for your game’s UI or HUD relies heavily on the aspect ratio of the screen. Because of the Arduboy’s ultra-wide screen, a lot of developers, including myself, have found it a lot easier to move
UI elements all the way to the left or right of the screen and have them in some small area. This works well on the Arduboy since the Arduboy’s screen simply doesn’t have a lot of vertical space to sacrafice.
If you’ve played my game, Circuit Dude, you’ll know the level indicator and temperature gauge are on the right side of the screen.
I think @olikraus’s Little Rook Chess is a great example of a square game that is being implemented in a wide screen and utilizing space comfortably. @olikrau actually has all the move information written out along the right-side while the entire left side of the screen is the chess board. It would have been very unwise to put those words at the top or bottom and require the chess board to be squeezed into a smaller area. A lot of side scrollers rely heavily on being able to see what’s in front of the player. I cannot think of a better example than Sonic, which a lot of people actually really enjoy dispite several BAD Sonic titles over the years. It’s a great source to look at to see what went well and what didn’t and how you could avoid those issues in your own game development.
to be continued next page >> Page 12 of 53
Let’s start with one of my favorite Sonic games, Sonic Advance, which was released on the Gameboy Advance. This game was so similar to the originals, but the graphics and mechanics were slightly updated. It really did retain its charm and comfort. It didn’t feel overwhelming to play and I was able to properly see what’s in front of me as I was running through the levels. Of course, jumping over enemies and spikes along the way. Because the game focuses on speeding through the game, you’ll need to learn to react to obstacles appearing in your way. This is great on widescreen devices because the wider the screen, the sooner you’ll see an obstacle in your way, no matter how fast you’re running.
I still play that Sonic game from time to time because of how fun it is and how much love went into making the game. Well, it appeared that the reception for Sonic Advance was so big that the team working on it was asked to make a version called Sonic N for the Nokia N-Gage, which was a smartphone/videogame hybrid that came out after the Gameboy Advance. It ended up selling poorly because consumers had so many complaints about it, even after several hardware revisions. For its time, the amount of pixels on the N-Gage’s screen was fine and it had full 3D games, which was a big feature for its time, but one major complaint was the screen’s aspect ratio.
See, the screen was vertical, like many smart phones of its time, instead of wide like the Gameboy Advance’s. This made a lot of games not feel as comfortable when porting them over and Sonic Advance was no exception. In fact, this was probably the second worst port of a Sonic game.
Even though Sonic N had all the same levels, same graphics, and ran perfectly fine on the N-Gage, the screen’s aspect ratio resulted in a shorter field of view... Someone playing this game on the N-Gage has a lot smaller chance to be able to react to an obstacle in the way because the screen is not as wide. I tried to find two screenshots that showed the same position. If you notice the N-Gage’s screenshot, there’s only 2 rings visible in front of the player while someone on the Gameboy Advance version could see 3. This minor difference changed the entire feel of a great Sonic game. to be continued next page >> Page 13 of 53
I bring up both the Pac-Man and Sonic example to explain how important it is for someone that is porting a game to consider the aspect ratio of the screen that they’re putting a game onto. The Arduboy might be great for a game like Sonic because its screen is so wide, but may be really bad for a game like the original Donkey Kong game may not port so well from the arcade since verical height is so important to that game. One way to solve this issue, as I’ve stated before, is to change the layout of the game, but another may be re-imagining the game with a side-scrolling perspective insider of relying on a taller screen. A plane shooting game like 1942 may be great for arcade cabinets with taller screens, but it could work on the Arduboy if the gameplay was simply rotated 90 degrees, which is why TEAM a.r.g.’s game, Sirène, works so well and is so comfortable! They’ve found a way to comfortably use similar gameplay on a completely different kind of screen in a comfortable mannor by taking these issues into consideration.
Imagine if you were working on Sonic N for the N-Gage. How would you solve the issue of not being able to see far enough ahead? One thing that you could try is adjusting the sizing of everything and “zooming out.” But this has some risks.
Let’s talk about the WORST port of a Sonic game and our final component, which is sizing. Now, dispite what you, as a reader, may or may not think about Sonic 2 for the Sega Genesis, it was incredibly popular when it came out and many people really enjoyed it, including myself. I recently picked up Sonic Mania on my PS4 and was astounded by how well the gameplay matched Sonic 2. The game’s simple UI had the same layout as Sonic 2, and even though the aspect ratio has changed from the Sega Genesis (4:3) to modern widescreen displays (16:9), it actually helped because the player has more room to see ahead of Sonic.
What I’ve done is attached 2 screenshots of Sonic. The first is Sonic Mania and the second is Sonic 2 for the Sega Genesis. The black bars along the sides indicate the change in aspect ratio. Believe it or not, Sonic’s sprites take up the same percentage of the screen between the two, relatively speaking. They did not zoom in or out to take advantage of the extra space on screen or anything like that.
to be continued next page >> Page 14 of 53
With that being said, let’s go back to Sonic N... We mentioned earlier that “zooming out” may help in some way solve the problem the N-Gage had with its aspect ratio. Well, it’s possible, but it would have meant that the player’s spite would be a lot smaller, and this could make things harder to see. (Remember when I mentioned Little Rook Chess’s board size getting too small?) But, I suppose it could have been possible. It’s something I would have wanted to experiment with as a developer if I was on the team working on it. But what would happen if we went the other way? Sonic Mania had a bigger aspect ratio than Sonic 2, so would it then be logical to think that Sonic Mania could have had bigger sprites to compensate for this? Well, yes, but the charm of the game would have been lost like it was in “Sonic the Hedgehog Genesis,” which came out for the Gameboy Advance in 2006, over 4 years after Sonic Advance. It was a remake of the original Sonic the Hedgehog from 1991. This version of Sonic has been considered by many to be not only the worst port of any Sonic game, but also one of the worst videogame ports of all time. Yes, the music was bad and yes there were glitches, but playing through the game, it just didn’t feel right to players and critics.
GBA version as the Gensis version in order to preserve the same appearance as the original. Because Sega is a company and their aim is to maximize profits and lower production costs, they decided to go with the choice that would save them the most amount of money- They did not remake new sprites, but instead used the old ones, that where way bigger than the sprites used in Sonic Advance and covered more of the screen than Sonic 2 or Sonic Mania. This created the appearance that everything in this version of the game was ‘zoomed in.’
In the above screenshot, you can see how much of the screen Sonic is taking up in the red box while the Sonic 2 and Sonic Mania sprite sizes are indicated with the blue lines. Simply put, Sonic took up WAY too much space in the screen and when you directly compare them, this is even more apparent.
The Gameboy Advance’s screen is 240×160 pixels while the Sega Gensis’s resolution was 320x224. If the developers were re-creating Sonic from the Sega Genesis to put on the Gameboy Advance, they obviously had a choice they had to make between 1) Remaking smaller versions all of the originals sprites so that the percentage of screen space that Sonic’s sprite took up was the same on the GBA as the original Genesis version, or 2) Using the same exact sprites that were designed for a larger resolution on the
to be continued next page >> Page 15 of 53
This is a simple case of sizing gone wrong in order to save a few dollars or to avoid recreating some sprites. Porting over a game from one device to another can sometimes works well, but if you’re porting it over directly, the sprites and layouts may need to be adjusted. The Arduboy’s small resolution pretty much makes it impossible to port sprites over from systems like Gensis or Gameboy Advance, but it is something to keep in mind when porting an Arduboy game to a new system in the future.
Thanks for reading over this. I know it was a bit of a ramble, but I think these concepts are important for developers to consider when working on games. I don’t know what I’m going to write about next month in Arduboy Magazine, but I’m sure it will have to do with comfort or UI in some way or another, so this will be a good stepping stone to that! Anyway, if you’re interested in following me on Twitter, check me out @crait and let me know if you liked this article or not. See ya’!
Circuit Dude is a top-down, tile-based puzzler with 100 levels. Help Circuit Dude build his ultimate secret invention by plugging in chips, moving blocks, rotating walls, and much more!
Get it on Steam here!
Page 16 of 53
Laying Pipes - Part 2 by @filmote In last article we put together the framework for retrieving our puzzle from PROGMEM, populating our multidimensional array and rendering the puzzle on the screen.
In this month’s article we will concentrate on the actual game play itself – still in the confines of a puzzle that is 5 x 5 cells however we start to add functionality that will allow us to move to large puzzles later. The play logic consists of two distinct modes – selecting a node and then laying pipe.
Structures Before diving into the first game playing mode, I want to draw your attention to a number of Data structures that I have defined to capture game play information. The first, Node, represents a node in the puzzle and contains three variables that hold the coordinates and cell value respectively. The second, Player, uses this new data type to capture the player’s game state including the selected node and highlighted node. struct byte byte byte };
The final structure holds the current puzzle details including the puzzle number being played, the game’s dimensions - represented as a Node so we have width (x) and depth (y) dimensions – and the puzzle board itself. As development of the game continues we will add additional details to this structure to hold the current level and offset information to render the board centrally in the screen. For the moment it only has three members.
Node { x; y; value;
struct Player { Node highlightedNode; Node selectedNode; } player; struct Puzzle { byte index; Node maximum; byte board[9][9]; } puzzle; to be continued next page >> Page 17 of 53
The setup() method has also been modified and I have hardcoded the puzzle number and dimensions of the first puzzle. void setup() { arduboy.begin(); arduboy.clear(); puzzle.index = 0; puzzle.maximum.x = 5; puzzle.maximum.y = 5; }
Game Play
Mode One: Node Selection
Navigating around the board In the last article, we looked at a function to render the board based on a populated puzzle. If you recall, it rendered the pipes and the grid markers for the board. I have added the code below to highlight the player’s selected cell by simply drawing a square. render the board centrally in the screen. For the moment it only has three members. void renderBoard() { ... arduboy.drawRect(player.highlightedNode.x * GRID_WIDTH, player.highlightedNode.y * GRID_HEIGHT, GRID_WIDTH + 1, GRID_HEIGHT + 1, WHITE); } When selecting a node, the user can scroll anywhere within the confines of the board. The following code tests whether the player has pressed on of the four directional buttons and if the move is valid. If so, the x or y coordinates are altered and the board rendered again.
to be continued next page >> Page 18 of 53
void play_NoSelection() { if (arduboy.justPressed(LEFT_BUTTON) && player.highlightedNode.x > 0) if (arduboy.justPressed(RIGHT_BUTTON) && player.highlightedNode.x < puzzle.maximum.x - 1) if (arduboy.justPressed(UP_BUTTON) && player.highlightedNode.y > 0) if (arduboy.justPressed(DOWN_BUTTON) && player.highlightedNode.y < puzzle.maximum.y - 1)
{ player.highlightedNode.x--; } { player.highlightedNode.x++; } { player.highlightedNode.y--; } { player.highlightedNode.y++; }
... renderBoard(); }
Selecting a Node After navigating around the board, the player can do one of two things – select a node that has never been played before or select a node that already has pipe connected to it.
The play handling code in the previous section handles the pressing of the ‘A’ button and handles these two scenarios.
void play_NoSelection() { ... if (arduboy.justPressed(A_BUTTON) && isNode(player.highlightedNode.x, player.highlightedNode.y)) { A new function, nodeAlreadyPlayed(), tests to see if the node has already been played and if so clears the board of the previously laid pipe for the selected node.
It then copies the selected nodes value and coordinates into the player.highlightNode structure.
if (nodeAlreadyPlayed(getNodeValue(player.highlightedNode.x, player. highlightedNode.y))){ clearBoard(getNodeValue(player.highlightedNode.x, player.highlightedNode.y)); player.selectedNode.value = getNodeValue(player.highlightedNode.x, player.highlightedNode.y); player.selectedNode.x = player.highlightedNode.x; player.selectedNode.y = player.highlightedNode.y; gameState = STATE_NODE_SELECTED; } else { If the node has not been played before, the selected nodes value and coordinates into the player.highlightNode structure. to be continued next page >> Page 19 of 53
player.selectedNode.value = getNodeValue(player.highlightedNode.x, player.highlightedNode.y); player.selectedNode.x = player.highlightedNode.x; player.selectedNode.y = player.highlightedNode.y; gameState = STATE_NODE_SELECTED; } } renderBoard(); } After a node is selected, the game state is changed to STATE_NODE_SELECTED and the board rendered.
In a later article, we will add some additional code to allow the user to abort the game by pressing the ‘B’ button.
Mode Two: Laying Pipe Pipe Images
This is particularly important with the sprites height as the rendering code in the Arduboy library is optimized for writing to the screen eight vertical bits at a time.
IAfter selecting a node, the player can then lay the pipe between it and the corresponding node. Unlike the node selection mode, the player can only navigate between the nodes using the blank cells of the puzzle. The pipes cannot cross each other or touch other nodes. The player can backtrack on the pipe they are currently laying and this feature has resulted in a lot of code.
The pipe sprites are shown below. You may have noticed that they are 12 pixels by 12 pixels which allows us to join the nodes together properly by taking into account the additional padding the grid places around the 8 x 8 node. When rendering out a 12 x 12 sprite, the four rows at the bottom of the sprite (coloured in grey below) will also be rendered out and potentially overwrite sections of the screen we wish to preserve.
But before we get to that code, let’s look at the graphics for the pipes. In the last article I went into a lot of detail on how to define the sprites and suggested you strive to always create sprites with dimensions that are multiples of 8.
0
1
2
3
4
5
6
7
8
9
10
11
12
0
1
1
2
2
3
3
4
4
5
5
6
¢¢¢¢¢¢¢¢¢¢¢¢
1
2
3
4
5
7
8
8
9
9
10
10
11
11
12
12
13
7
8
9
10
11
12
¢ ¢ ¢ ¢ ¢ ¢ ¢ ¢ ¢ ¢ ¢ ¢
6
7
6
The last sprite is a mask and we will use it to render the 12 x 12 sprites without upsetting the remainder of the screen. 0
1
2
3
4
5
2 3
7
8
9
10
11
12
1
2
3
2 3 4
¢
5
0 1
¢
4
6
6
¢ ¢ ¢
1
5
¢¢¢
6
7
7
8
8
9
9
10
10
11
11
12
12
13
13
13
14
14
14
14
15
15
15
15
16
16
16
16
pipe_horizontal
pipe_vertical
pipe_corner_TL
pipe_corner_TR
0
1
2
3
4
5
6
7
8
9
10
11
12
0
1
2
3
4
5
6
7
8
9
10
11
12
0
1
1
1
2
2
2
3
3
3
4
4
4
5
5
5
6
¢¢¢¢
6
¢¢¢
6
1
2
3
4
5
6
7
8
9
10
11
4
5
6
7
8
9
10
11
12
¢ ¢ ¢ ¢ ¢ ¢¢¢¢
12
¢¢¢¢¢¢¢¢¢¢¢¢ to¢be ¢¢¢¢¢¢¢¢¢¢¢ ¢¢¢¢¢¢¢¢¢¢¢¢ ¢¢¢¢¢¢¢¢¢¢¢¢ ¢¢¢¢¢¢¢¢¢¢¢¢ ¢¢¢¢¢¢¢¢¢¢¢¢
continued next page >> Page 20 of 53
15
15
15
15 pipe_corner_BR
16
16
16
16
pipe_horizontal
pipe_vertical
pipe_corner_TL
pipe_corner_TR
0
1
2
3
4
5
6
7
8
9
10
11
12
0
1
2
3
4
5
6
7
8
9
10
11
12
0
1
1
1
2
2
2
3
3
3
4
4
4
5
5
5
¢¢¢¢
6
¢
7
10 11 12
6
¢
8
¢ ¢ ¢ ¢
9
¢¢¢
7
¢
8
6
9 10 11 12
7
¢
8
¢ ¢ ¢ ¢
9 10 11 12
12
0
¢¢¢¢¢¢¢¢¢¢¢¢ ¢¢¢¢¢¢¢¢¢¢¢¢ ¢¢¢¢¢¢¢¢¢¢¢¢ ¢¢¢¢¢¢¢¢¢¢¢¢ ¢¢¢¢¢¢¢¢¢¢¢¢ ¢¢¢¢¢¢¢¢¢¢¢¢ ¢¢¢¢¢¢¢¢¢¢¢¢ ¢¢¢¢¢¢¢¢¢¢¢¢ ¢¢¢¢¢¢¢¢¢¢¢¢ ¢¢¢¢¢¢¢¢¢¢¢¢ ¢¢¢¢¢¢¢¢¢¢¢¢ ¢¢¢¢¢¢¢¢¢¢¢¢
1
1
2
3
4
5
6
7
8
9
10
11
1
2
3
pipe_corner_B
4
5
6
7
8
9
10
11
12
2 3 4 5
¢¢¢¢
6
¢
7
¢
8
¢ ¢ ¢ ¢
9 10 11 12
13
13
13
13
14
14
14
14
15
15
15
15
16
16
16
16
pipe_corner_BR
pipe_corner_BL
pipe_mask
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x40, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
Recording Pipe Details 0
1
2
3
4
5
6
7
8
9
10
11
12
The six images above are all that is required to 2 3 draw the pipes on screen. However, to allow 4 the player to backtrack on pipes they have 5 just laid we need¢to¢ introduce the concept of 6 ¢¢ 7 ¢ direction. The constants below expand the 8 ¢ six9 images into twelve by adding a direction ¢ 10 indicator. ¢ 1
11 12
#define 13 14 #define 15 16 #define #define #define #define #define #define #define #define #define #define #define #define
¢ ¢
NOTHING PIPE_HORIZONTAL_LR PIPE_HORIZONTAL_RL PIPE_VERTICAL_TB PIPE_VERTICAL_BT PIPE_CORNER_TL PIPE_CORNER_LT PIPE_CORNER_TR PIPE_CORNER_RT PIPE_CORNER_BL PIPE_CORNER_LB PIPE_CORNER_BR PIPE_CORNER_RB NODE
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x40, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
For example, the horizontal pipe has a left to right variant, PIPE_HORIZONTAL_LR, and a right to left variant, PIPE_HORIZONTAL_RL. Likewise the corner or elbow images have variants detailing from which side they entered and then left.
0 1 2 3 4 5 6 7 8 9 10 11 12 15
These constants are then used as indices in an array of pipes as shown below. We looked at pointer arrays in the previous article when constructing the node array.
You may also have noticed that each image is included in the array twice – once for each direction.
const byte* pipes[] = {pipe_nothing, pipe_horizontal, pipe_horizontal, pipe_vertical, pipe_vertical, pipe_corner_TL, pipe_corner_TL, pipe_corner_TR, pipe_corner_TR, pipe_corner_BL, pipe_corner_BL, pipe_corner_BR, pipe_corner_BR};
to be continued next page >> Page 21 of 53
So how do we record the player’s pipe laying? After selecting a node, the player can then lay the pipe between it and the corresponding node. Unlike the node selection mode, the player can only navigate between the nodes using the blank cells of the puzzle. The pipes cannot cross each other or touch other nodes. The player can backtrack on the pipe they are currently laying and this feature has resulted in a lot of code.
In the previous article, we used the initBoard() function to read a nominated puzzle from the PROGMEM into our multidimensional array. We then used the renderBoard() function to display the board. The array and output is shown below. Notice that the node numbers have been logically ORed with the value 0xF0 to produce values such as 0xF1, 0xF2 and so on. This is allows us to determine that the cell is a node (‘F’) and its value.
If the player then moves right, the highlightNode.x value is increased by one and the underlying board[][] array is updated with 0x11. This is made up from the constant PIPE_HORIZONTAL_LR and the node value of the selected node, 1.
This indicates that the player moved from the left to the right to get to the position they are now in. After rendering the board, the pipe is shown horizontal.
The player moves right a second time and the board and highlight are updated as in the previous step.
The board[][] array is updated again and the screen is rendered.
This time the player clicks the down arrow. The second horizontal pipe laid in the last step must be changed to an elbow that indicates that the pipe was laid from the left to the bottom of the cell – the cell is updated with
the constant PIPE_CORNER_LB (Decimal 10 or hex 0xA) and the selected node value, 1. The new pipe is also recorded in the board[][] array. to be continued next page >> Page 22 of 53
The same thing happens again when the player clicks the right arrow. The vertical pipe laid in the last step must be changed to an elbow that indicates that the pipe was laid from the top to the right of the cell â&#x20AC;&#x201C; the cell
is updated with the constant PIPE_CORNER_TR (0x7) and the selected node value, 1. The new pipe is also recorded in the board[][] array.
The following four functions use bit manipulation to determine whether a cell is a node or a pipe and what its value is. bool isNode(byte x, byte y) { return (puzzle.board[y][x] & 0xF0) == 0xF0; } byte getNodeValue(byte x, byte y) { return (puzzle.board[y][x] & 0x0F); } bool isPipe(byte x, byte y) { return (puzzle.board[y][x] & 0xF0) > 0x00 && (puzzle.board[y][x] & 0xF0) != 0xF0; } byte getPipeValue(byte x, byte y) { return (puzzle.board[y][x] & 0xF0) >> 4; } to be continued next page >> Page 23 of 53
The following function tests the board to see if a node has been played (ie. has piped laid from it to its corresponding node) by looking for any cell that is not a node but has a corresponding value. bool nodeAlreadyPlayed(byte value) { for (byte y = 0; y < puzzle.maximum.y; y++) { for (byte x = 0; x < puzzle.maximum.x; x++) { if (getNodeValue(x, y) == value && !isNode(x, y)) { return true; } } } return false; } The pipe laid between two nodes can be cleared by looking for cells that have the nominated value but are not nodes. void clearBoard(byte nodeValue) { for (byte y = 0; y < puzzle.maximum.y; y++) { for (byte x = 0; x < puzzle.maximum.x; x++) { if (getNodeValue(x, y) == nodeValue && !isNode(x, y)) { puzzle.board[y][x] = NOTHING; } } } }
Valid Moves After selecting a node, the player can simply move anywhere there is a blank cell or onto the matching node. As mentioned before,
what complicates this is that the player can backtrack on the pipe they are currently laying. to be continued next page >> Page 24 of 53
Before any other tests, the code tests to ensure The code below determines whether a that the chosen play is still within the confines particular move is valid. It accepts the of the board – if not the function returns a parameters including the direction of travel false. (enumerated by constants), the node original selected and the coordinates we wish to move to. bool validMove(byte direction, Node selectedNode, char x, char y) { // Off the grid! if (x < 0 || x >= puzzle.maximum.x || y < 0 || y >= puzzle.maximum.y) return false; Next the function determines whether the cell nominated is empty or the corresponding node to the one selected.
If either case is true, the function returns a true.
// Is it a clear cell or the matching node? if ( (!isNode(x,y) && getPipeValue(x,y) == NOTHING) || (isNode(x,y) && getNodeValue(x,y) == selectedNode.value && (x != selectedNode.x || y != selectedNode.y)) ) return true; Finally, if the move hasn’t been ruled out so far we test to see if the move is actually one where the player is turning back on the pipe he has just laid. This is determined by checking the supplied direction against the cell we are about to move into.
For example, if the player has pressed the ‘UP’ button and the position they are planning to move to indicates a move from either the top, left or right to the bottom of the square then we are backtracking - likewise for the other directions.
// Is the pipe turning back on itself? switch (direction) { case (UP): switch (getPipeValue(player.highlightedNode.x, player.highlightedNode.y)) { case PIPE_VERTICAL_TB: case PIPE_CORNER_RB: case PIPE_CORNER_LB: return true; } break; case (DOWN): ... case (LEFT): ... case (RIGHT): ... } return false; } to be continued next page >> Page 25 of 53
The previous function are all we need to complete the second mode of the game, laying pipe between nodes.
The full code is a large case statement that handles navigation in all four directions â&#x20AC;&#x201C; I have only included the code that handles the left button press.
void play_NodeSelected() { if (arduboy.justPressed(LEFT_BUTTON)) { if (validMove(LEFT, player.selectedNode, player.highlightedNode.x - 1, player.highlightedNode.y)) { If the player has pressed the left button and it is a valid move then we must handle the rendering of the new pipe based on the previous pipe. In this way we can change
straight pipes to curved elbows as necessary or handle the user backtracking on tiles they have just laid.
switch (getPipeValue(player.highlightedNode.x, player.highlightedNode.y)) { If the player has pressed left and the previously laid pipe was from the right, then we are backtracking on pipe.
The updatePipeWhenReversing() function reverts the previously laid pipe to a straight one if necessary. No new pipe is laid.
case PIPE_HORIZONTAL_LR: case PIPE_CORNER_TR: case PIPE_CORNER_BR: updatePipeWhenReversing(player.highlightedNode.x - 1, player. highlightedNode.y); setCellValue(player.highlightedNode.x, player.highlightedNode.y, NOTHING, NOTHING); break; If we are continuing from the left, there is no need to update the previously laid pipe and we laid a new horizontal pipe. case case case if
PIPE_HORIZONTAL_RL: PIPE_CORNER_TL: PIPE_CORNER_BL: (!isNode(player.highlightedNode.x - 1, player.highlightedNode.y)) { setCellValue(player.highlightedNode.x - 1, player.highlightedNode.y, PIPE_HORIZONTAL_RL, player.selectedNode.value);
} break; If we are have changed from an upward or downward movement to the left, the previous pipe is converted to an elbow that exits to the left and we lay a new horizontal pipe.
to be continued next page >> Page 26 of 53
case case case if
PIPE_CORNER_LT: PIPE_CORNER_RT: PIPE_VERTICAL_BT: (!isNode(player.highlightedNode.x - 1, player.highlightedNode.y)) { setCellValue(player.highlightedNode.x - 1, player.highlightedNode.y, PIPE_HORIZONTAL_RL, player.selectedNode.value);
} setCellValue(player.highlightedNode.x, player.highlightedNode.y, PIPE_CORNER_BL, player.selectedNode.value); break; case case case if
PIPE_CORNER_LB: PIPE_CORNER_RB: PIPE_VERTICAL_TB: (!isNode(player.highlightedNode.x - 1, player.highlightedNode.y)) { setCellValue(player.highlightedNode.x - 1, player.highlightedNode.y, PIPE_HORIZONTAL_RL, player.selectedNode.value);
} setCellValue(player.highlightedNode.x, player.highlightedNode.y, PIPE_CORNER_TL, player.selectedNode.value); break; If the previous cell was a node (the starting node) then we simply lay a horizontal pipe. case NODE: setCellValue(player.highlightedNode.x - 1, player.highlightedNode.y, PIPE_HORIZONTAL_RL, player.selectedNode.value); break; } If we are about to move onto a matching node to the one we have previously selected then the pipe has been successfully completed. Game play reverts to the first
mode STATE_NO_SELECTION unless all of the pipes have been successfully completed, at which point the game concludes.
if (isNode(player.highlightedNode.x - 1, player.highlightedNode.y) && getNodeValue(player.highlightedNode.x - 1, player.highlightedNode.y) == player.selectedNode.value) { clearSelection(); gameState = STATE_NO_SELECTION; // Is the level finished ? if (isPuzzleComplete()) { gameState = STATE_GAME_OVER; } } player.highlightedNode.x--; } } to be continued next page >> Page 27 of 53
A variation of the above code is repeated for the remaining three directions. if (arduboy.justPressed(RIGHT_BUTTON)) { ... if (arduboy.justPressed(UP_BUTTON)) { ... if (arduboy.justPressed(DOWN_BUTTON)) { ... RenderBoard(); }
Whereâ&#x20AC;&#x2122;s the code? The sample code detailed in this article can be found in the repository at https://github. com/filmote/Pipes_Article2. The game is playable now but only with the first puzzle as it is hardcoded in the setup() routine. There are two puzzles coded in the Puzzles.h file and
you can play the other one by changing the line to: puzzle.index = 1; Or even better, why not try create your own puzzles by adding to the puzzles_5x5 array?
Next Month.. In the next article, we will add a level selection dialogue and add some harder puzzles.
Laying Pipes - Part 2 (Callouts) by @filmote
Structures A structure is simply a way of grouping related variables together into a single construct. A structure can be passed to a function as a single unit.
An example of a structure is shown below and is used in the Pipes program. It represents a node in the puzzle and contains three variables that hold the coordinates and cell value respectively. to be continued next page >> Page 28 of 53
struct byte byte byte };
Node { x; y; value;
Once defined, a new variable can be created using it as a datatype as shown below. The members of the structure can be referenced directly to assign or retrieve their values. Node aNode; aNode.x = 3; aNode.y = 4; int x = calc(aNode); int calc(Node theNode) { return theNode.x * theNode.y; } The declaration and initialization code above could have been expressed in a single line as shown below. The members of the structure must be initialised in the sequence they were declared in the structure definition.
Not all members must be initialized and the second example is also valid and the value member would be initialised to 0.
Node myNewNode = {3, 4, 5}; Node myNewNode = {3, 4}; Structures can contain other structures. The Pipes game contains a simple structure for the playerâ&#x20AC;&#x2122;s game state including the selected node and highlighted node as shown below. struct Player { Node highlightedNode; Node selectedNode; } Initialising a structure with embedded structures is shown below. Note that the initializer for each node is contained within its own braces {}. Player player = { {3, 4, 5}, {3, 4 } };
// init highlighted node // init selected node
Stay tuned for next monthâ&#x20AC;&#x2122;s article. Page 29 of 53
Collision Detection
Detecting Collisions between two Images by @filmote
In my article last month, I covered the basics of collision detection using the Arduboy2 library. This method is very fast but suffers in that a collision might be reported when only the â&#x20AC;&#x2122;white spaceâ&#x20AC;&#x2122; surrounding your images cross. Visually the objects may miss each other completely!
This article describes a method of comparing two images at the bit (or pixel) level to see if any of the set bits collide. I have included a sample application in my repository, https://github.com/filmote/ CollideImage, for you to download and try. This article will discuss the collision detection between the two images below:
Assume they are rendered on the screen as shown below:
to be continued next page >> Page 30 of 53
The first image (the circle) starts at position (2, 3) and extends 16 bits either way to (17, 18) inclusive. The second image (the triangle) starts at position (13, 5) and extends 16 pixels either way to (28, 20) inclusive.
To understand if there has been a collision, we first need to calculate the overlapping section, as highlighted by the red rectangle below. For clarity, the pixels that have actually collided are coloured red.
The overlapping rectangle area can be determined by applying the following formulas: overlap_left overlap_top overlap_right overlap_bottom
= max( img1.x, img2.x ); = max( 2, 13 ); = 13; = max(img1.y, img2.y ); = max( 3, 5 ); = 5; = = = =
min(img1.x + img1.width, img2.x + img2.width ); min( 2 + 16, 13 + 16 ); min( 18, 29 ); 18;
= = = =
min(img1.y + img1.width, img2. y + img2.width ); min( 3 + 16, 5 + 16 ); min( 19, 21 ); 19;
Overlapping rectangle starts at (13, 5) in the top left-hand corner and extends to (17, 18) in the bottom right-hand corner, inclusive. Note that our calculation returned a right and bottom value that were exclusive (18, 19).
To retrieve the pixels that overlap each other, we need to work out which section of the image the overlapping rectangle applies to. The calculations above returned screen coordinates and take into account that the image is not located at (0, 0). to be continued next page >> Page 31 of 53
If you recall, the images are defined as an array of bytes (or uint8_t) with the first two bytes specifying the width and height of the image and the remainder the image data itself. Each byte of the image data represents a vertical column of 8 pixels with the least significant bit at the top. The bytes are drawn as 8 pixel high rows from left to right, top to bottom.
When the end of a row is reached, as specified by the width value, rendering continues on the following line. The definition for the ‘circle’ and ‘triangle’ images are shown below. The bytes in red are those that we will ultimately be retrieving and comparing. Those in inverse red / white are the bytes that will be described in the following section:
const uint8_t PROGMEM circle[] = { 0x10, 0x10, 0xE0, 0xF8, 0xFC, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, // Row 1 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xFC, 0xF8, 0xE0 , 0x07, 0x1F, 0x3F, 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, // Row 2 0xFF, 0xFF, 0xFF, 0x7F, 0x7F, 0x3F, 0x1F, 0x07 ,
};
const uint8_t PROGMEM triangle[] = { 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0xC0 , 0xF0, 0xFC, 0xFF, // Row 1 0xFF, 0xFC, 0xF0, 0xC0, 0x00, 0x01, 0x03, 0x07, 0xC0, 0xF0, 0xFC, 0xFF, 0xFF , 0xFF, 0xFF, 0xFF, // Row 2 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xF0, 0xC0,
};
When calculating the rectangle of the image that we are interested in, it is important to realize that although the image is defined as rows of bytes – with each byte representing a vertical column of 8 bits (or pixels) – the rectangle we will want to retrieve will often result in us needing to mask some of the bits out of the data.
In the image below, the solid red lines represent the overlapping area between the two images. The dashed lines highlight the bytes that will be retrieved. As you can see, the top two bits in the upper row of data are not needed when detected if there is a collision or not. The entirety of the bottom row is included in the overlapping area and hence no bits need to be masked off.
to be continued next page >> Page 32 of 53
The following calculations determine the same coordinates relative to each image: img1_left
= = = img1_right = = = img1_top_row = = = img1_top_bit = = = img1_bottom_row = = = img1_bottom_bit = = =
(overlap_left – img1.x); 13 – 2; 11; (overlap_right - img1.x); 18 – 2; 16; (overlap_top - img1.y) / 8; (5 – 3) / 8; 0; (overlap_top - img1.y) % 8; (5 – 3) % 8; 2; (overlap_bottom - img1.y - 1) / 8; (19 – 3 – 1) / 8; 1; (overlap_bottom - img1.y) % 8; (19 – 3) % 8; 0;
Rather than calculating top and bottom coordinates, we have calculated a left and right index which we can then use while iterating through the array of image data. We have also calculated a top row and the number of bits to mask – row 0 with the first two bits masked. Likewise, we have calculated the bottom row and any bits to mask – row 1 and no bits.
to be continued next page >> Page 33 of 53
Repeating the process on the triangle image produces a similar result although this time we will be masking off the lower 2 bits of the bottom row.
img2_left
= = = img2_right = = = img2_top_row = = = img2_top_bit = = = img2_bottom_row = = = img2_bottom_bit = = =
(overlap_left – img2.x); 13 – 13; 0; (overlap_right – img2.x); 18 – 13; 5; (overlap_top – img2.y) / 8; (5 – 5) / 5; 0; (overlap_top – img2.y) % 8; (5 – 5) % 8; 0; (overlap_bottom – img2.y - 1) / 8; (19 – 5 – 1) / 8; 1; (overlap_bottom – img2.y) % 8; (19 – 5) % 8; 6;
With these calculations done, we can retrieve the data from the image definition. The first byte to be read is determined using the code below. It takes into account a starting byte that may be on a row other than the first and adds two additional bytes to cater for the
two dimension bytes that start all image definitions. The width of the image is retrieved by reading the first byte of the image itself.
to be continued next page >> Page 34 of 53
#define IMG_DATA_OFFSET 2 uint8_t w1 = pgm_read_byte(&img1[0]); uint8_t w2 = pgm_read_byte(&img2[0]); int16_t i1 = = int16_t i2 = =
(img1_top_row * w1) + img1_left + IMG_DATA_OFFSET; 13; (img2_top_row * w2) + img2_left + IMG_DATA_OFFSET; 2;
The first byte to be read from the circle is calculated to have an index of 13 and the triangle at index 2. A quick look at the triangle reveals that the first 4 bytes of the image are empty (0) and thus will not demonstrate the process well.
Imagine we have skipped along four bytes without a collision and we are now testing:
int16_t i1 = 17; int16_t i2 = 6; Looking back at the image definitions, we can see that the value retrieved at index 17 of the circle is 0xE0 which can be represented as a binary number as 1110 0000. The value at index position 6 of the triangle is 0xC0 or 1100 0000. const uint8_t PROGMEM circle[] = { 0x10, 0x10, 0xE0, 0xF8, 0xFC, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, // Row 1 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xFC, 0xF8, 0xE0,
};
0x07, 0x1F, 0x3F, 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, // Row 2 0xFF, 0xFF, 0xFF, 0x7F, 0x7F, 0x3F, 0x1F, 0x07,
const uint8_t PROGMEM triangle[] = { 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xF0, 0xFC, 0xFF, // Row 1 0xFF, 0xFC, 0xF0, 0xC0, 0x00, 0x01, 0x03, 0x07, 0xC0, 0xF0, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Row 2 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xF0, 0xC0,
};
to be continued next page >> Page 35 of 53
Our collision code compares 8 bits from each image together to see if there is a collision. However, as we discovered when working out the overlapping portion that the top of the area will not always align with the underlying bytes of data. In our calculations, the top two bits of the circleâ&#x20AC;&#x2122;s data would be discarded leaving us with only 6 bits. Reading the first byte from the triangle image would return a full 8 bits as no bits were to be discarded. To provide a valid comparison, we need to ensure we are comparing the same number of bits from each image.
To achieve, this, we need to read and append the first two bits of the byte directly below the one we are processing to complete our eight bits. Depending on the top and bottom bit masking values, we may need to mask the top and / or bottom. The first byte value is retrieved from each image using the code below:
uint16_t d1 = pgm_read_byte(&img1[i1]) & (img1_top_row == img1_bottom_row && img1_bottom_bit != 0 ? pgm_read_byte(&lookup[img1_bottom_bit]) : 0xFF); uint16_t d2 = pgm_read_byte(&img2[i2]) & (img2_top_row == img2_bottom_row && img2_bottom_bit != 0 ? pgm_read_byte(&lookup[img2_bottom_bit]) : 0xFF); Breaking it down reveals how it works. First the top row of data is retrieved: i1 = 17; i2 = 6; uint16_t d1 = = uint16_t d2 = =
pgm_read_byte(&img1[i1]) 0xE0; pgm_read_byte(&img2[i2]) 0xC0;
If we are only reading one row of data or we are reading the last row of data, we may need to mask the bottom of pixels off. In both images we are reading the first of two lines, so the remainder of the statement is ignored. We will cover this little bit of code later on in the article.
As mentioned earlier, it may be necessary to retrieve the byte of data directly below the one currently being assessed to build a full complement of 8 bits. This logic only applies if the calculate top bit is not zero and only if we are not yet processing the last row in the image.
if (img1_top_bit > 0 && img1_top_row < img1_bottom_row) { d1 = d1 | ((pgm_read_byte(&img1[i1 + w1]) & (img1_top_row + 1 == img1_bottom_row ? pgm_read_byte(&lookup[img1_bottom_bit]) : 0xFF )) << 8); } Again, breaking the code down we can see the condition that tests for the top bit being non-zero and ensures we are not processing the last row of the image. if (img1_top_bit > 0 && img1_top_row < img1_bottom_row) { â&#x20AC;Ś to be continued next page >> Page 36 of 53
If this evaluates to true, the byte of image data directly below the one retrieved previously is also retrieved. Its index can easily be determined by adding the width of the image to the index of the byte retrieved previously. … (pgm_read_byte(&img1[i1 + w1]) … If we are only reading one row of data or we are reading the last row of data, we may need to mask the bottom of pixels off. This is achieved with the second portion of the statement that calculates a mask to apply to the second value just retrieved.
Breaking it down further - if the second byte of image data was retrieved from the last row of data, we may need to apply a mask to remove the lower bits ..
… img1_top_row + 1 == img1_bottom_row … .. then we work out the mask to apply. If no mask is needed, a default mask is returned that ensures no pixels are affected. … ? pgm_read_byte(&lookup[img1_bottom_bit]) : 0xFF … To speed up to the process, I have defined the mask values as constants rather than calculating them on the fly. To mask the lowest bit of an image, the mask needs to be 0111 1111. To mask the lower two bits, the mask needs to be 0011 1111 and so
on. These same masks can be defined as 1111 1111 >> {number of rows to mask} or 0xFF >> {number of rows to mask}. As you can see, this is exactly how the lookup has been defined.
const uint8_t PROGMEM lookup[] { 0xFF >> 8, 0xFF >> 7, 0xFF >> 6, 0xFF >> 5, 0xFF >> 4, 0xFF >> 3, 0xFF >> 2, 0xFF >> 1}; Finally, the value retrieved is shifted to the left 8 times (the equivalent of multiplying it by 256) and logically OR’ed to the image data we retrieved from the top line to produce a 16-bit number with the high order byte representing the second row
of image data and the low order byte representing the first row. If there was a top bit to mask, this is done be right shifting the resultant 16-bit number and then masking all but the lower 8 bits to create a single byte.
d1 = d1 | ( lower byte ) << 8); d1 = (d1 >> img1_top_bit) & 0xFF; Let’s plug in the numbers from our two images. The data from the top line of the image was determined to be 0xE0 and 0xC0 for the circle and triangle respectively. The other important values are repeated in the next page:
to be continued next page >> Page 37 of 53
img1_left img1_right img1_top_row img1_top_bit img1_bottom_row img1_bottom_bit
= = = = = =
11; 16; 0; 2; 1; 0;
img2_left img2_right img2_top_row img2_top_bit img2_bottom_row img2_bottom_bit
= = = = = =
0; 5; 0; 0; 1; 6;
i1 = 17; i2 = 6; d1 = 0xE0; d2 = 0xC0; if (img1_top_bit > 0 && img1_ top_row < img1_bottom_row) { d1 = d1 | ((pgm_read_byte(&img1[i1 + w1]) & (img1_ top_row + 1 == img1_bottom_row ? pgm_read_byte(&lookup[img1_bottom_bit]) : 0xFF )) << 8); } if (img2_top_bit > 0 && img2_ top_row < img2_bottom_row) { d2 = d2 | ((pgm_read_byte(&img2[i2 + w2]) & (img2_ top_row + 1 == img2_bottom_row ? pgm_read_byte(&lookup[img2_bottom_bit]) : 0xFF )) << 8); }
As the top bit on image 2 is equal to zero, the code is not executed for it. For the first image (the circle) reading the value at pgm_read_byte(&img1[i1 + w1]) returns 0x07.
We are retrieving this value from the last row of the image so we can further simplify the code to:
d1 = d1 | ((0x07 & pgm_read_byte(&lookup[img2_bottom_bit])) << 8); Which, after the lookup, becomes: d1 = = = = =
d1 | ((0x07 & (0xFF >> 2)) << 8); d1 | ((0x07 & 0x3F) << 8); d1 | (0x07 << 8); 0xE0 | 0x0700; 0x07E0;
As you can see, we have created a 16 bit value from the two bytes we have read. The value retrieved for the second line, 0x07 was masked using the value 0x3F (binary 0011 1111) which had the effect
of masking off the two most significant bits of the value. These bits correspond to the two bottom pixels we wanted to remove.
to be continued next page >> Page 38 of 53
The value 0x07E0 can be represented as 0000 0111 1110 0000 which you can see is the right most column of our image.
We have already masked off the lower two bits â&#x20AC;&#x201C; although they happened to be blank anyway.
The final piece of the puzzle is to shift the result to remove any unwanted pieces at the top of the image and to truncate the data to 8 bits for comparison. d1 = = = =
(d1 >> img1_top_bit) & 0xFF; (0x07E0 >> 2) & 0xFF; (0x1F8) & 0xFF; 0xF8 or 1111 1000;
to be continued next page >> Page 39 of 53
Applying the same logic to the triangle results in:
d2 = (d2 >> img2_top_bit) & 0xFF; = (0xC0 >> 0) & 0xFF; = 0xC0 or 1100 0000; Now we have our two pieces of data we can compare them together to see if they collide at all. Converting the two values 0xF8 and 0xC0 to binary, make it easier to visualise the collision.
The image data from the circle, 0xF8, converts to 1111 1000 whereas the triangleâ&#x20AC;&#x2122;s value 0xC0 converts to 1100 0000. The lower two bits collide!
if ((d1 & d2) > 0) { return true; } The two bits we have detected are highlighted in the image below.
Congratulations, your sprites have collided and you have stuck this far into the tutorial!
to be continued next page >> Page 40 of 53
If no collision has been detected, we increase the index and do it all again. When the right most column is reached, the index is wrapped around to the next
row and processing continues until a match is detected or all columns and rows have been compared.
if (i1 < (img1_top_row * w1) + img1_right + IMG_DATA_OFFSET) { ++i1; ++i2; } else { if (img1_top_row < img1_bottom_row) { ++img1_top_row; ++img2_top_row; i1 = (img1_top_row * w1) + img1_left + IMG_DATA_OFFSET; i2 = (img2_top_row * w2) + img2_left + IMG_DATA_OFFSET; } else { }
return false;
}
Earlier on when we retrieved the very first pieces of image data, we skipped over a section of code as it did not apply and I said we would cover it later. If you were paying attention, you may have noticed that this code was identical to the code that masks the bottom pixels when retrieving the second row of data. This code exists when retrieving the first piece of data to cater for the scenario where the overlap occurs within a single row of data. Simple right? A sample project can be found in my GitHub repository here. This method seems quite laborious and I welcome input to speed it up. You will see in the sample project that I perform a simple check using the standard Arduboy2 method before doing any pixel - level work to ensure that the code returns promptly if there is no chance of a collision. When
a possibility of a collision is detected, the code processes the overlapping rectangle left to right and then top to bottom and will return on the first collision. If the collision is detected in the first comparison the code will again return quickly however if a collision is not detected until the last byte of data is compared, then the routine must process a lot of data. This may sound like a significant drawback but need not be. If you are detecting the collisions as two objects move around the screen â&#x20AC;&#x201C; say one or two pixels at a time â&#x20AC;&#x201C; the overlapping area when two image rectangles collide will be quite small minimizing the amount of processing required. The code sample provided can easily be extended to accept two images and their corresponding masks and the detection code altered to execute over the mask instead.
Page 41 of 53
by @mainy1009 Making pixel art is one of my favourite things to do. It’s an easily recognisable and replicable type of art. It’s been used in the most addicting games and amazing art. Many people think that pixel art is a tedious and complex type of art, when actually, it’s an easily recognisable and replicable type of art. So here’s 3 pixel styles that you can make right now!
Style #1: Retro Style. BEGINNER
I would say that this is the easiest method and style to make as it uses simple shapes and requires no shading, unless you want to add shading. Step 1: Create your body using rectangles, no complex shapes. There should be 5 rectangles, 1 for each arm, 1 for each leg, 1 for the body and 1 for the head. This should take you 1-5 minutes. Step2: Shape the boxes into body parts. In most cases, the top of the body part is thicker than the bottom. Step3: Fill in the details + Facial features. Tips Make eyes as 3x3 squares with 1 black pixel in the middle. Make noses by drawing a circle/ oval that has been cut in half.
Style #2: ‘Wire-light pixel art’. MEDIUM I enjoy this style because it has a minimalist colour pallet and there is an unbelievable amount of customisability. It can be used to make simple art or complex art. Step 1: I like to start with the nose. I usually use a curvy ‘L’ shape. Then draw the eyes above it, to the left and right. Then draw the mouth underneath it. Draw the chin and neck to finish the face. Step2: Draw the hair, the hair is incredibly customisable, it can be spikey, flat, curly, short, long, the list goes on and on. Step3: The body is very easy, use the rectangle technique from before, to make a square and curve it the way you want. Then use the same technique for the legs and arms. To add depth to your character, move the ‘front’ leg partially behind the ‘back’ leg. You can even push the bicep section of the arm outward, to make him/her look muscly.
Step4: Add patterns and symbols, to give your characters more style and personality.
can be spikey, flat, curly, short, long, the list goes on and on. This is aiscomic I made I was findingfrom before Step3:The body very easy, use thewhen rectangle technique my style. It includes two very simple Then use the same hnique tec for the legs and arms. To add depth to your characters. No shading is needed in this outward, behind the ‘ back ’ leg. You can even push the bicep section of the arm to Step4:Add patterns and symbols, to give rs your more characte style and personality style.
Style #3: 64bit style. Advanced
This style is what I use for pixel art animation. It can be hard if you are just beginning, but looks amazing if you’ve been making pixel art for a short while. It uses shading, proportions, layers and ‘skeleton planning’. An amazing pixel artist, who’s almost perfected this style is @ Skaz_. Step 1: The skeleton can be done in many ways, I prefer to have 1 thick lines with alternating colours representing each different part of the body. Green/Blue for the ‘back’ leg and arm and yellow/red for the forward body parts. I chart out the chest, sliding the dividing line left if they are looking away from our perspective and right if they are looking towards us, given that they are looking to stage left. Step 2: Orange represents objects and anything that flows. I haven’t shown the bandana as an object, even though I should have, which shows everyone makes mistakes. The skeleton stages are used so you can make the animation without shaping and shading the body.
Step 3: Fill out the body with limbs 2-3 wide ‘ limbs, and the torso should be around 6 ’ _. pixels wide. Try and alternate poses with different characters. Step4: Shading is/can be very tedious and complex. I suggest having 3 layers; light, mid-tone and dark. If you are making a still piece, more layers can be added, if you’re up for it. When making shiny object, I prefer to make horizontal or diagonal light beams, rather than diagonal. I hope you learned from this, if you did, send me your work on Twitter! Happy pixeling!
Are you a pixel artist? Done some Arduboy Fan art? Wish to have your work shared with the Arduboy community? Send arduboymag a message through the Arduboy community page or a DM on Twitter!
EXPLORE:
Maker
Faire
Tokyo
A short picture journal of some of the activities at Maker Faire Tokyo last month. It was held at Tokyo Big Sight.
GYOZA! And these amazing chocolate biscuits.. Maker Faire diet.
Landed in Tokyo early in the morning and a TV crew was interviewing random tourists why they were coming to Japan. So I unrolled the banner and showed them why!
Alot of space to make your own thing to bring home, and be interactive with the exhibitors
The fair was huge! approximately 25,000 people (probably more) turned up.. The organisers got around in bicycles
Page 44 of 53
8-Bit cup noodles! This was not at the Maker Faire, but in a convenience store. watching me watching you playing Tetris
So happy to see everyone spending time at the booth and playing with an Arduboy
Page 45 of 53
Thank you everyone who came down - some of them were Kickstarter backers!
Cat tax
Did anyone else attend? What did you enjoy the most? Hello Switch Science!
Page 46 of 53
Up coming events ! Join the Arduboy team at these events in October! Come say hi to us, try out some demo units (if you do not have an Arduboy yet), ask us questions, and enjoy special event discounts. ;)
Portland Retro Gaming Expo Portland, ORegon usa Retro Gaming Expo, Inc. is a Portland, Oregon-based 501(c)4 non-profit cooperative organization dedicated to creating awareness of, and appreciation for classic video and arcade games through the presentation of events and conventions that celebrate the historic contribution video games have made and continue to make in popular culture. Look out for the 7th Tetris World Championship.. Itâ&#x20AC;&#x2122;s extra special this year ;)
Get tickets here: http://www.retrogamingexpo.com/
Page 47 of 53
EXPLORE:
In this section we’ll ask the same 10 questions to a different developer in every new issue.
Questions with game developers by @celinebins
This issue we interview Japanese @inajob. Creator of the recent Poop Panic (for the Arduboy Game Jam), Petitseq, and Nanoseq. Read on to find out how he got started with the Arduboy before even getting one!
1. Where did you find out about Arduboy the first time?
2. What is the first program/game you created for Arduboy?
I’ve created circuits and programs with Arduino since 2009, and I found Arduboy May 2015 on Kickstarter. Because I was interested in Arduboy, I tried to make a clone. I found schemas of Arduboy and Gamebuino, and bought all the parts in Akihabara.
Because I’ve already made an electric instrument with Arduino, I made “nanoseq”. It is a simple music sequencer program.
I finally made it, and exhibited the Arduboy clone in Maker Faire Tokyo 2016. I met Kevin there and bought Arduboy. That was the first time I met a/the real Arduboy.
3. What programs do you use for creating Arduboy apps/games? Bitmap Editor made by emutyworks is simple but enough bitmap editor for developping Arduboy games. I don’t use the Arduino IDE, but use PlatformIO instead. PlatformIO is a more developer friendly build system. I love it. Git is an important tool for developping anything. Version management is a very complex probrem for everyone. Git helps us. to be continued next page >> Page 48 of 53
4. Did you have app/game developer experience before Arduboy.
8. What is the next app/program for Arduboy we can expect?
Yes, I’ve made PC games during junior high school days as a hobby.
I’m making action & puzzle game. It is the ported of the game for PC that I made in my university days.
5. What do you read to learn how to code for Arduboy? When I was young, I had subscribed to a magazine that introduced readers to simple games in source code level. That experience helps me to create Arduboy games. And Official Arduino documents are also useful.
6. What app/game do you currently have on your Arduboy. There is an original game that I am working on that is under construction on my Arduboy.
7. What app/program is your favorite? Omega Horizon made by N eoretro is my favorite game. That is very high quality graphics and sounds and nice game balance.
9. What app/game would you love to see on the Arduboy? I want to play a game like Tamagotchi. Tamagotchi is the virtual pet breeding game. Japanese company sell that when I was young. Tamagotchi is like Arduboy at the point that is tiny size electric game console.
10. What is your best tip for other people who want to start creating apps/games for Arduboy? Simple is the best. I recommend to create “fortune cookie program”. That is simply shows the random fortune every boot. I also recomend hacking other games. For example you change some graphics sprites.
Read inajob’s blog to find out more on how he used the same Arduboy 128 x 64 OLED to run both Arduboy and Gamebuino games on a 1GB SD card!
Are you a developer and want to be featured here? Reach out to the Magazine through Twitter DM @arduboymag Page 49 of 53
EXPLORE:
Customized
Arduboys
@eried customizesArduboys as prizes for the Dualog Hackathon 0.2! Szymon (Baradziej) was organizing this year Hackathon and he was thinking on the prizes for the first place. I ask him if he wanted to give the winners a boring trophy that is instantly forgotten or something really specialâ&#x20AC;Ś like an Arduboy. He said a boring trophy was OK so the story ends here.
First attempt Our company updated the branding time ago, and we had a lot of mugs with the old logo to try. We tried with extremely hot water, thinner, a mix of thinner and water, spraying it with a bottle, heat gunâ&#x20AC;Ś our results were not very successful:
Of course, he was kidding. Who is not going to fall in love after looking at an Arduboy in the wild?.
Hydrographics for the first time Szymon ordered five Arduboys and started to think on a way to get the looking unique. https://gethydrographics.com/
Figure : Failed attempt on a mug
Success... Kind of Nothing was working. Frustrated, Szymon checked the hydrographic paper description again. The idea was to use slightly warm water, not hot. We tried again with water at about 39C, the result was finally a success:
Figure : Hydrographic sheets
Figure : First success, directly on the Arduboy cover to be continued next page >> Page 50 of 53
The result was not perfect. There is a lot of variables, dipping speed, angle, temperature. We got some bubbles but with only four days to finish it was impossible to get more attempts. The best dip we got was with the following conditions: • Warm water • Paper with the ink facing up, with boundaries of masking tape around • Slow but steady dip of the Arduboy back cover in a shallow angle: 35-50 degrees
Logos Having the default program in the Arduboys is too boring, so I created a pixelated version of the “winner” sticker, added music and the starfield demo.
Happy winners
Figure : Another successfully attempt on the Arduboy back cover, now with our CEO’s face
Finishing up the Arduboys After one day, the transfer paper was dry. The Arduboys were sprayed with clear acrylic spray four times and sanded with a fine (400 grit) sand paper in between covers.
Figure : Hackathon 0.2 first day. Six to seven teams of four to six people are going to create something new in two days.
Figure : End of the second day. My team (From the left: Me, Katrine, Simon. Not in the photo: Jesper) won... so I won my custom prize back (lol but yay! more Arduboys)
Thank you Erwin for sharing your customised Arduboy experience with us! How else would you customise an Arduboy? Page 51 of 53
#arduboy: Arduboy
is
friendly!
Works great on itâ&#x20AC;&#x2122;s own but also compatable with other electronics (if you have the right code). And if you donâ&#x20AC;&#x2122;t have the right code, it still makes for a nice photo op.
@handheld_game_collector
@letstalkretro
@gamesyouloved
@raceydella
@leotheamazing
@tokyopotato
@mmeidl78
@ginerale666
@kolosslava
Post your photos with Arduboy on Instagram and Twitter with #arduboy. We want to feature them in this segment! Page 52 of 53
Thank
you!
Thank you for reading the Magazine! Hope you enjoyed it as much as we did putting it together. We want to know how to make Volume 9 better than the last, so write in to us to tell us what you think!
https://twitter.com/arduboymag