I’m writing a Pac-Man clone in ActionScript 3 using Adobe Animate (previously Flash) and could use some advice on movement and speed. According to Pitman's Pac-Man dossier, Pac-Man moves at approximately 75.75 pixels per second. In my code below you can see I am setting the FPS of the project to 38 (half the max speed) and moving him 2 pixels per frame to achieve 100% speed. The animation is smooth and works great. The issue is when I try the 80% speed and therefore have to, on some frames, reduce the number of pixels down from 2 in order to "slow" him down. Assuming I program the ghosts using the same system, there are plenty of other speeds I have to achieve from 40% to 105% in increments of 5. When you reduce any of the steps (see code) to slow Pacman down, there's a noticeable stutter. I can try putting the FPS of the app at 76fps and move at 1 pixel per frame but I have a feeling that reducing some steps to 0 will produce a similar result but it may not be as noticeable at higher frame rates. Is there any advice anyone can give me to move in a different way? I'm kind of restricted here with moving each frame by whole numbers with no fractions.
import flash.display.MovieClip;
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.display.Stage;
import Configure.Config;
public final class Pacman extends MovieClip {
private static var _instance:Pacman;
private var gridx;
private var gridy;
private var config:Config;
private var decx;
private var decy;
private var offsetX;
private var offsetY;
private var dir:Number; //pacman's direction. 0=left, 1=up, 2=right, 3=down
private var stepTable:Array = [];
private var speedCycle = 0;
public var step:Number = 1;
public function Pacman(_x:Number=112, _y:Number=212) {
// constructor code
if( _instance ){
throw new Error("Singleton... use getInstance()");
}
_instance = this;
this.addEventListener( Event.ENTER_FRAME, this.enterFrameHandler );
x = _x; y= _y;
offsetX = 0;
offsetY = 24;
config = Config.getInstance();
dir = 0; //left
initStepTable();
}
public static function getInstance():Pacman{
if( !_instance ){
new Pacman();
}
return _instance;
}
private function enterFrameHandler(e:Event)
{
var _parent = MovieClip(parent);
var moved = false;
var speedStr:String;
speedStr = stepTable["80"];
step = Number(speedStr.charAt(speedCycle));
updateGridPosition()
if(config.isKeyDown(37)) {
if( canMoveLeft() ) {
x -= step;
if(decy != .5){
if(decy < .5) y += step;
if(decy > .5) y -= step;
}
rotation = 0;
//play();
moved = true;
} else {
}
}
if(config.isKeyDown(38) ) {
if( canMoveUp() ) {
y -= step;
if(decx != .5){
if(decx < .5) x+=step;
if(decx > .5) x-=step;
}
rotation = 90;
//play();
moved = true;
}
}
if(config.isKeyDown(39) ) {
if ( canMoveRight() ) {
x += step;
if(decy != .5){
if(decy < .5) y += step;
if(decy > .5) y -= step;
}
rotation = 180;
//play();
moved = true;
}
}
if(config.isKeyDown(40) ) {
if( canMoveDown() ){
y += step;
if(decx != .5){
if(decx < .5) x+=step;
if(decx > .5) x-=step;
}
rotation = 270;
//play();
moved = true;
}
}
//stop();
updateGridPosition()
_parent.changeTxtPacX( String(x ) );
_parent.changeTxtPacY( String(y) );
_parent.changeTxtPacGrid( String(gridx + ":" + gridy) );
_parent.changeTxtPacDec( String(decx + ":" + decy) );
speedCycle++; if(speedCycle > 18) speedCycle=0;
}
private function updateGridPosition(){
gridx = ( x - offsetX ) / 8;
gridy = ( y - offsetY ) / 8;
decx = gridx - Math.floor( gridx );
decy = gridy - Math.floor( gridy );
}
private function canMoveLeft():Boolean {
var leftcontent = MovieClip(parent).getGridContent( Math.floor((gridx + .375 ) - 1), Math.floor(gridy) );
if( leftcontent != "#" ) return true;
return false;
}
private function canMoveUp():Boolean {
var upcontent = MovieClip(parent).getGridContent( Math.floor(gridx), Math.floor((gridy + .375) - 1));
if( upcontent != "#" ) return true;
return false;
}
private function canMoveRight():Boolean {
var rightcontent = MovieClip(parent).getGridContent( Math.floor((gridx - .5) + 1), Math.floor(gridy) );
if ( rightcontent != "#" ) return true;
return false;
}
private function canMoveDown():Boolean {
var downcontent = MovieClip(parent).getGridContent( Math.floor(gridx), Math.floor((gridy - .5) + 1));
if( downcontent != "#" ) return true;
return false;
}
private function initStepTable():void
{
/*
Todo:
*/
stepTable["100"] = "2222222222222222222"; // 100% speed
stepTable["80"] = "2222022220222202220";
//stepTable[2] = "0222222202222222"; //Pacman speed at 2-4 (85% of 75 pixels = 64.40 pixels/sec. We get 66.36 using our speed calc)
//stepTable[3] = "1111111111111111"; //Pacman speed at 5-20
//stepTable[4] = "1111111111111111"; //Pacman speed at 21+
}
} //end class
} // end package
Usually in rendering a game, you should not tightly couple your game movement, processing, physics, or animations etc to your framerate. Because framerate can change, and usually you want those other things to be based on real world time.
Most game frameworks or engines handle this by using a Variable Time Step (aka using "delta time" between frames) when updating things. Jonas Tyroller actually has quite an in depth video on the subject, while being language agnostic: https://www.youtube.com/watch?v=yGhfUcPjXuE
I would fix your fps to something smooth. I recommend 60fps. Then handle all the speeding up and slowing down in your code.
I won't write all your code out, but the main part is to use the time difference between your frames in the movement calculations. So you probably need to work out how to fit this concept into your own system. Here is the important parts