|
|
|
|
| Welcome, Guest | Home | Search | Login | Register | |
| Author | Mac port of "Attack of the PETSCII Robots"... (Read 39553 times) | ||||||||||||||
|
Jatoba
256 MB ![]() ![]() ![]() ![]() ![]() Posts: 270 System 9 Newcomer! |
Reply #30 on: June 18, 2024, 12:42
I forgot to mention, but much in that double-buffered attempt was shuffled and reshuffled many times over, so the code there only shows the "last attempt". I tried commenting out SetPortWindowPort(window); because we want to work with the offscreen buffer instead, but then I would not get the moving square at all, and so that code is still there and active. I wonder if I also should have done away with SetPort(savedPort); entirely in order for CopyBits and the rest of the code to do their magic... But I'm not sure yet. The following part was a total half-assed attempt at me trying to get things working pulling things straight from PETSCII when I was already burned out for the moment without trying to really understand it, and it probably makes 0 sense being in there: bpl=0; /*bpl=GetPixRowBytes(pm);*/ bpl=(unsigned int)((*pm)->rowBytes&0x3fff); dst=GetPixBaseAddr(pm); off=0; sbpl=0; for(r=0;r<0;r++) { for(b=0;b<sbpl;b++) { fread(&c,1,1,0); dst[off+b]=c; } off+=bpl; } Even if I pursue the "dirty rectangle" route, I will probably try to get the double-buffered version working first for the sake of my own knowledge with handling this approach with Quickdraw, and overall. EDIT: Argh, this is embarassing, I just realized one stupid thing I was doing wrong all along: I "erase" the rectangle original position, then update the screen, then paint it in the new position, then update it again. One obvious issue is that I was supposed to update only the second time, not both! I have a "bool" (char) to control which one I'm doing, so I just need to address this accordingly in my painting routine. D'oh! No double-buffering nor dirty-squaring would have fixed this mistake. I'll go back and clean my dirty laundry now. At least this sent me on quite a journey (that I still am walking), and that journey was going to be necessary eventually anyway. So that's good. |
||||||||||||||
Last Edit: June 18, 2024, 14:21 by Jatoba
|
lauland
|
512 MB ![]() ![]() ![]() ![]() ![]() Posts: 674 Symtes 7 Mewconer!
Reply #31 on: June 18, 2024, 15:14
|
Ah, I love the smell of code in the morning! Definitely getting there! The bit of code you copied isn't what you need...yet...it is actually the part that reads a raw bitmap from a file to an offscreen buffer. The fread should definitely not be in there! But you're close. Here's some rough pseudocode: (I hope this is right, it's early in the morning for me!) main() { init toolbox; open window; create offscreen buffer; loop(); delete offscreen buffer; close window; } loop() { while(running) { drawEverything(); if(itIsTimeToUpdateScreen) { SetPort(window); begin update; CopyBits(offscreen,window); end update; } handleEvents(); } } drawEveryhing() { lock the offscreen; SetPort(offscreen); drawTheBackground(); drawTheSprite(); unlock the offscreen; } drawTheBackground() { eraseRect(offscreen); // maybe draw a grid or a pattern instead? } ---- For now itIsTimeToUpdateScreen should just be a bool "true", but eventually you'll check the ticks and draw less often. With this method you never need to erase anything moving, redrawing the background in the offscreen (and then drawing the sprite over that) does it for you. The "double" part of double buffering, at least in this example, means you have two buffers, the one being drawn on, and the one the player is viewing (the window). On some systems you'd actually have two equal buffers and use hardware to switch between them. So you'd alternate on which you were drawing. The point being all the drawing takes place offscreen...so in theory there is still flickering, but it's where you can't see it! Like setting up a stage in play with the curtains closed...then you open them so the audience can see...close them, change the set, open them again. The audience never sees the workers moving things around. ---- The above just has a single offscreen buffer, since you're not copying blocks of pixels (blitting) yet, just drawing a line/square/dot for the sprite. If it did, there'd be a second source one created in main and you'd use something like the "bpl code" to set it up there. Then your drawTheSprite would be a CopyBits of just the player block you wanted from the second offscreen to the original one. This source bitmap would have different images of a guy walking, for example, and by choosing which player block you copied, the legs would move (even if you didn't change the coords where you drew, etc). Look at the .png's from PETSCII to see this. It actually uses many offscreen source bitmaps, separate ones for the font, the player, background bits, items, and more. It then has a single particular offscreen that "stores the scene". Blocks are copied from the different sources to that one. Then, in the loop, when it's time to draw the frame, there's another copy from that single offscreen to the window. So the buffers are: multiple sources "the current frame being drawn" the window ---- We'll cross the "Gameboy bridge" when we get to it, but know for now that video game hardware can be VERY different from general purpose personal computers. On a Mac, your screen is a bitmap and that's it. Video game hardware tends to have hardware sprites (so you don't need double buffering for them!) and the screen may be "block" or "tile" oriented...ie you can't just draw a pixel anywhere, but instead only (for example) 16x16 pixel blocks.
Last Edit: June 18, 2024, 15:19 by lauland
|
Jatoba
|
256 MB ![]() ![]() ![]() ![]() ![]() Posts: 270 System 9 Newcomer!
Reply #32 on: June 21, 2024, 18:53
|
So, I was able to fix the flicker with what I noticed earlier (paint two times, refresh screen only after both are done). I left a "TODO" for myself for double-buffering, dirty rectangles and other painting approaches. If time permits, I will definitely revisit this step (and the PETSCII code). Now, the current challenge: moving diagonally! Sounds simple, right? Maybe it is. I mean, e.g. moving diagonally up-right should involve pressing "up" and "right", then hold the keys to keep moving... Or so I thought. Well, moving ONCE works with up + right, but when holding both keys down for the "repeat" or "auto" event, it seems to only repeat the last registered key of the two, meaning I move either up OR right on repeat. I feel confident about figuring this one out myself, but thought I would share another "update" nonetheless. Let's see how this goes. EDIT: OK, this might be harder than I initially thought. I thought maybe I could do things like checking for "kUpArrowCharCode | kRightArrowCharCode", but those constants from Events.h are not what we call "bit flags", and so I cannot overlap the values like that. I'm not sure how to handle diagonal movement here, but I will keep trying to figure something out. EDIT 2: So, it seems the "autoKey" (key repeat) event only registers 1 key at a time, and that seemed like a game over for pressing 2 arrow keys for one instance of diagonal movement. I could use some other, single key for diagonal, but that would not feel "natural", and I wanted to use the arrow keys. I wondered if I could work with more than 1 event at once or in parallel, but that seems overly complicated and something that would not work... ... Then I discovered / remembered / realized I could simply use some local controls of my own to register if one of the 4 arrows keys were pressed in a keyDown event, AND add keyUp event checks for those same keys so that I know when one of the keys are no longer pressed. With this, I SHOULD be able to get the autoKey event to do what I want! Let's see. EDIT 3: It works! Something like this: while (!boolQuit) { if (WaitNextEvent(everyEvent, &event, 1, NULL)) { unsigned long keysPressed = event.message & charCodeMask; switch (event.what) { case keyDown: fprintf(debugf, "keyDown event below! Value of keysPressed: %d\n", keysPressed); if ((event.modifiers & cmdKey) && keysPressed == 'q') { /* Exit the loop when Command + Q is pressed */ boolQuit = 1; } else { switch (keysPressed) { case kUpArrowCharCode: boolIsUpArrowKeyPressed = 1; boolIsDownArrowKeyPressed = 0; PaintGrid(window, spriteRect, 1, 0, -4); break; case kDownArrowCharCode: boolIsDownArrowKeyPressed = 1; boolIsUpArrowKeyPressed = 0; PaintGrid(window, spriteRect, 1, 0, 4); break; case kLeftArrowCharCode: boolIsLeftArrowKeyPressed = 1; boolIsRightArrowKeyPressed = 0; PaintGrid(window, spriteRect, 1, -4, 0); break; case kRightArrowCharCode: boolIsRightArrowKeyPressed = 1; boolIsLeftArrowKeyPressed = 0; PaintGrid(window, spriteRect, 1, 4, 0); break; } } break; case keyUp: fprintf(debugf, "keyUp event below! Value of keysPressed: %d\n", keysPressed); switch (keysPressed) { case kUpArrowCharCode: boolIsUpArrowKeyPressed = 0; break; case kDownArrowCharCode: boolIsDownArrowKeyPressed = 0; break; case kLeftArrowCharCode: boolIsLeftArrowKeyPressed = 0; break; case kRightArrowCharCode: boolIsRightArrowKeyPressed = 0; break; } break; case autoKey: fprintf(debugf, "autoKey event below! Value of keysPressed: %d\n", keysPressed); if ((TickCount() - lastKeyTime) >= GetCaretTime()) { switch (keysPressed) { case kUpArrowCharCode: if (boolIsLeftArrowKeyPressed == 1) { PaintGrid(window, spriteRect, 1, -4, -4); } else if (boolIsRightArrowKeyPressed == 1) { PaintGrid(window, spriteRect, 1, 4, -4); } else { PaintGrid(window, spriteRect, 1, 0, -4); } break; case kDownArrowCharCode: if (boolIsLeftArrowKeyPressed == 1) { PaintGrid(window, spriteRect, 1, -4, 4); } else if (boolIsRightArrowKeyPressed == 1) { PaintGrid(window, spriteRect, 1, 4, 4); } else { PaintGrid(window, spriteRect, 1, 0, 4); } break; case kLeftArrowCharCode: if (boolIsUpArrowKeyPressed == 1) { PaintGrid(window, spriteRect, 1, -4, -4); } else if (boolIsDownArrowKeyPressed == 1) { PaintGrid(window, spriteRect, 1, -4, 4); } else { PaintGrid(window, spriteRect, 1, -4, 0); } break; case kRightArrowCharCode: if (boolIsUpArrowKeyPressed == 1) { PaintGrid(window, spriteRect, 1, 4, -4); } else if (boolIsDownArrowKeyPressed == 1) { PaintGrid(window, spriteRect, 1, 4, 4); } else { PaintGrid(window, spriteRect, 1, 4, 0); } break; } /* Update last key press time */ lastKeyTime = TickCount(); } break; case mouseDown: boolQuit = 1; break; } } } With this, things work just as I hoped: if you press e.g. arrow up, then arrow right, you walk diagonally northeast. Same for all combos. This also disallows pressing 2 opposite directions (up AND down, left AND right) simultaneously. There're still some minute details on how the movement feels that I will be addressing next, which by the way I know MANY games also have trouble with universally, but that I will fix here nonetheless for as smooth a moving experience as possible. For example, with this, if you hold up then right, you go northeast, but if you release up, yet keep holding right, you will NOT keep walking right, because the "autoKey" event is no more. Yet, the flag I added to indicate right is pressed will still remain at "true" (1), so I will try to work with that.
Last Edit: June 21, 2024, 21:53 by Jatoba
|
lauland
|
512 MB ![]() ![]() ![]() ![]() ![]() Posts: 674 Symtes 7 Mewconer!
Reply #33 on: June 22, 2024, 06:08
|
Very cool you figured that out! Funny how most keyboard controlled games just ignore the issue completely, but you nailed it! The best part is that now that you have that knowledge, you can use it in anything you write going forward. I haven't checked, but I'd guess many first person shooters, like Doom, do something similar, in that with W-A-S-D controls you'll almost always be holding down the W key to move forward, and (probably?) don't take your finger off it, while hitting the A and D to move to either side. ---- One of the first games I wrote was a Pac-Man clone, and it had interesting controls in that the game character keeps moving in whatever direction...so in the real arcade, players would learn to "lean" on the joystick in the next direction they wanted to go, while hugging a wall, moving towards a junction, that way they could make the turn smoothly and not have the character stop when he hit a wall. I didn't handle that AT ALL in my version! Let's say you're moving down a "corridor" from the right to the left, approaching the end, where it splits in a T, going up and down. The player would be leaning on the joystick towards "north-west"...the game would be getting "up" and "left" events, but ignoring the "up" ones (since there's a wall). That is until it reached the junction, and the player would ease off on the "left" and move the joystick just "up"....so, as events would look, the "keyDown" for "up" would just continue, while it'd (eventually) get a "keyUp" for "left". That would need code very very similar to what you wrote I think! ---- Obviously most old school joysticks were "digital" so they were just four switches for directions (just like four keys on the keyboard). I remember playing Q-Bert on the Commodore 64 was such a pain because you had to move the joystick diagonally, which was hard back in the day with low quality switches! I think some games that were "diagonal" based would have you actually rotate the joystick 45 degrees to make it easier! I believe there are some games that "knew" (assumed) the joystick could never go both "up and down" or "left and right" at the same time...so if you had a broken joystick and you were able to press those impossible combinations, you could sometimes trigger bugs and do things like move through walls, etc.
|
|
Pages: 1 2 [3]
|
| ||||
|
© 2021 System7Today.com. |

