Posts: 1,272
Threads: 119
Joined: Apr 2022
Reputation:
100
I'm finishing up a Pac-Man clone. The game runs at 60 frames per second just like the original. During game play the speed of both Pac-Man and the ghosts are modified by level events and by advancing to higher levels. The speed of these objects is adjusted in 5% increments resulting in a loss of 3 frames per second per 5% loss:
100% = 60FPS (the fastest any object travels)
95% = 57FPS (60 * .95)
90% = 54FPS (60 * .9)
85% = 51FPS (60 * .85)
...
...
40% = 24FPS (60 * .4) (the slowest any object travels - ghost in the tunnel on level 1)
The usual way I adjust speeds in games is to use MOD and skip frames when the outcome is 0 (zero):
IF Frames MOD 20 THEN ... (every 20th frame will be skipped resulting in 95% or 57FPS)
IF Frames MOD 10 THEN ... (every 10th frame will be skipped resulting in 90% or 54FPS)
IF Frames MOD 5 THEN ... (every 5th frame will be skipped resulting in 80% or 48FPS)
..
.. Etc.
This can also be done in reverse for lower frame rates:
IF Frames MOD 20 = 0 THEN ... (all but three frames will be skipped resulting in 3FPS)
IF Frames MOD 10 = 0 THEN ... (all but six frames will be skipped resulting in 6FPS)
..
.. And so on
My problem is that neither of these methods will yield 85%(51FPS), 60%(36FPS), 55%(33FPS), 45% (27FPS), and 40%(24FPS) all of which I need.
There must be a simple formula I am overlooking to use within a 60FPS loop:
Object.FPS = 24
Frame = 0
DO
_LIMIT 60
Frame = Frame + 1
( If Object.FPS multiplied by some magical number= current frame then draw it ... formula here)
LOOP
I know I could set up individual frame counters for every object and skip, say, every 9th frame to achieve 85%. However, the original Pac-Man arcade machine had 16K of ROM and 2K of RAM and I can't imagine this was the procedure used with such limited space.
I also realize that I could simply use single precision numbers for x,y and add the percentage ( x! = x! + .85 : 85% for instance ) to get the desired outcome, but again, using single precision values in that era would have been a no-no given the speed over head.
How did those early programmers do this with Integers? Is there a formula I'm overlooking? Help me Obi-Wan math wizards, you are my only hope. I've stared at this for far too long now. My brain hurts.
Posts: 2,698
Threads: 327
Joined: Apr 2022
Reputation:
217
11-11-2022, 11:06 PM
(This post was last modified: 11-11-2022, 11:16 PM by SMcNeill.)
Why not use _LIMIT to set the FPS and then calculate position/graphic based on it?
Mouth_Open = X times per second or Limit / x
Mouth_Closed = Not Opened
Move = x pixels per second or Limit / X
Mouth should open 5 times per second? On limit 30, it's open starting at 0, 6, 12, 18, 24 on your loop counter, for 3 loop count, for example.
Posts: 3,980
Threads: 177
Joined: Apr 2022
Reputation:
220
I think of speed as pixels per frame something moves over the screen: speed = SQR(dx^2 + dy^2) if you want a formula
dx = speed * cos(HeadingInRadians)
dy = speed * sin(HeadingInRadians) ' more formulas
If fastest thing moves 5 pixels per frame then for 85% of it is .85 * 5 = 4.25 for speed and so on...
If the heading is up or down it's all dy.
Likewise if only left or right it's all dx.
b = b + ...
Posts: 1,272
Threads: 119
Joined: Apr 2022
Reputation:
100
(11-11-2022, 11:06 PM)SMcNeill Wrote: Why not use _LIMIT to set the FPS and then calculate position/graphic based on it?
Mouth_Open = X times per second or Limit / x
Mouth_Closed = Not Opened
Move = x pixels per second or Limit / X
Mouth should open 5 times per second? On limit 30, it's open starting at 0, 6, 12, 18, 24 on your loop counter, for 3 loop count, for example.
Yes, I could do that. That's another good method I can add to the two I pointed out above. I'm trying to find the system the original programmers used. This web site contains "The Pacman Dossier":
https://www.gamedeveloper.com/design/the...an-dossier
If you scroll down about a quarter of the way you'll see a diagram of the PacMan and Ghost speeds and when they are to occur. The "Norm Dots" and "Fright Dots" columns can be ignored, they are simply the result of skipping one frame every time Pacman eats a pellet.
Each of these speeds come out to an equal FPS when you multiply 60 by the percentage. There must be a simple algorithm I'm overlooking that relates to this.
I found the original source code to Pacman (Z80 ASM) but oh lord, I have not dabbled in Assembler in years, and never in Z80, only x86. I suppose if a resident math wizard doesn't come to my rescue I could always wade through the thousands of lines of ASM code to see if I can glimpse the inner workings. Ugh.
Posts: 1,272
Threads: 119
Joined: Apr 2022
Reputation:
100
(11-11-2022, 11:36 PM)bplus Wrote: I think of speed as pixels per frame something moves over the screen: speed = SQR(dx^2 + dy^2) if you want a formula
dx = speed * cos(HeadingInRadians)
dy = speed * sin(HeadingInRadians) ' more formulas
If fastest thing moves 5 pixels per frame then for 85% of it is .85 * 5 = 4.25 for speed and so on...
If the heading is up or down it's all dy.
Likewise if only left or right it's all dx.
Sure, sure. I use those same formulas for a lot of my vector work. This is similar to the approach I mentioned using single precision values. Completely doable. I'm hoping to find the way the original programmers did it. My goal, if it's attainable, is to have the ghosts move in the exact same patterns as the arcade machine so established patterns of play will work. I'm very close. A pattern I know *almost* works. I just need to get these timings exact. I'll add an option to the game that seeds the RND generator so established patterns won't work either creating a whole new experience.
That Pac-Man Dossier I mentioned above has darn near everything one needs to clone the game perfectly, except for the method of FPS adjustment.
Posts: 2,179
Threads: 222
Joined: Apr 2022
Reputation:
104
A completely different method is using an inner timer...
For your 51 FPS example:
Code: (Select All) z2 = TIMER
DO
_LIMIT 60
i = i + 1
IF ABS(z2 - TIMER) >= .06 THEN z2 = TIMER: s = s + 1
LOOP UNTIL i = 60
PRINT i, i - s, s, 100 - ((s / (i - s)) * 100)
END
Pete
Posts: 1,272
Threads: 119
Joined: Apr 2022
Reputation:
100
(11-12-2022, 12:15 AM)Pete Wrote: A completely different method is using an inner timer...
For your 51 FPS example:
Code: (Select All) z2 = TIMER
DO
_LIMIT 60
i = i + 1
IF ABS(z2 - TIMER) >= .06 THEN z2 = TIMER: s = s + 1
LOOP UNTIL i = 60
PRINT i, i - s, s, 100 - ((s / (i - s)) * 100)
END
Pete
Hmmm.. Interesting. My son saw me working on the code this afternoon and suggested something similar. He said the way the Nintendo worked was that sprites had two pixel layers. The outer pixel layer was a one to one relationship with the pixels on screen. The inner pixel count was used to move the sprite using smaller steps that would not show up on screen. Your approach seems to be along this line.
Posts: 8
Threads: 0
Joined: Apr 2022
Reputation:
0
For 85%, what if you do:
IF (2*frames) MOD 18 THEN ...
Would that work?
Posts: 2,698
Threads: 327
Joined: Apr 2022
Reputation:
217
11-12-2022, 02:07 AM
(This post was last modified: 11-12-2022, 02:08 AM by SMcNeill.)
(11-12-2022, 01:47 AM)TerryRitchie Wrote: (11-12-2022, 12:15 AM)Pete Wrote: A completely different method is using an inner timer...
For your 51 FPS example:
Code: (Select All) z2 = TIMER
DO
_LIMIT 60
i = i + 1
IF ABS(z2 - TIMER) >= .06 THEN z2 = TIMER: s = s + 1
LOOP UNTIL i = 60
PRINT i, i - s, s, 100 - ((s / (i - s)) * 100)
END
Pete
Hmmm.. Interesting. My son saw me working on the code this afternoon and suggested something similar. He said the way the Nintendo worked was that sprites had two pixel layers. The outer pixel layer was a one to one relationship with the pixels on screen. The inner pixel count was used to move the sprite using smaller steps that would not show up on screen. Your approach seems to be along this line.
Seems like a count## = count## + 51 / 60 would work as well. Just move the sprite on every integer increase of count.
0 -- move
0.8 -- don't move
1.6 -- move
2.4 -- move
3.2 -- move
4.0 -- move
4.8 -- don't move
(For example, depending on whatever 51/ 60 actually works out to be.)
Posts: 2,179
Threads: 222
Joined: Apr 2022
Reputation:
104
(11-12-2022, 01:53 AM)LEM Wrote: For 85%, what if you do:
IF (2*frames) MOD 18 THEN ...
Would that work?
I was thinking in similar terms, along the lines of work I did with greatest common factors. With the one of 85% I came out with 2.5. Dang, not an integer, right? Well whogas? Let's try that mulitpiled to the frames MOD 20... Pretty close to 85%!
Input 2.5,20
Code: (Select All) DO
CLS
INPUT x, y: j% = 60
DO
frames = frames + 1
PRINT frames
_LIMIT 60
j% = j% + 1
IF (j% * x) MOD y = 0 THEN
' skip frame
skip = skip + 1
ELSE
' do stuff...
cnt = cnt + 1
END IF
IF j% = x THEN j% = 0
LOOP UNTIL frames = 60
PRINT "Total frames processed ="; cnt; " Skipped ="; skip; "Percentage = "; LTRIM$(STR$(100 - (skip / cnt * 100))); "%";
SLEEP
CLEAR
LOOP
+1 to LEM. I think he's on to something.
Pete
|