I'm trying to make a basic game in Flash and Actionscript 3.
As of now, I've been working on a smooth game loop but ran into problems. I tried to implement a fixed time-stamp loop seen: http://gafferongames.com/game-physics/fix-your-timestep/ and in Flixel.
The main issue that I have right now is that moving a simple object across the screen produces noticeable stuttering. I am aiming for a smoother experience but can't seem to figure out what the issue is.
The main loop is called on an Event.ENTER_FRAME at 60 fps.
These variables are instantiated first:
public var total:uint = 0;
public var fixedDT:Number = 1000.0/60.0; //Shoot for 60 FPS in MS
public var accumulator:int = 0;
public var maxAccumulation:uint = 120;
This is the main loop on every ENTER_FRAME:
//Most times are in ms
var mark:uint = getTimer();
var elapsedMS:uint = mark-total;
total = mark;
accumulator += elapsedMS;
if(accumulator > maxAccumulation){
accumulator = maxAccumulation;
}
while(accumulator > fixedDT){
step();
accumulator = accumulator - fixedDT;
}
//Convert from ms to secs. to interpolate graphics drawing (linear interpolation)
renderGameState(accumulator/fixedDT/1000.0);
step() is just updating every game-object with the fixed delta-time. The game object update function is simple and is as follows:
//First part is just updating the previous position for graphic interpolation
position.x += velocity.x*deltaTime;
position.y += velocity.y*deltaTime;
For rendering, I am just drawing bitmap.copyPixel. The graphical interpolation I mentioned is using a basic linear interpolation function that uses prev./curr. position and deltaTime to calculate the drawX/Y.
public function render(bitmap:BitmapData, deltaTime:Number, xOff:Number, yOff:Number):void{
this.x = lerp(prevPosition.x,position.x,deltaTime) + xOff;
this.y = lerp(prevPosition.y,position.y,deltaTime) + yOff;
bitmap.copyPixels(bitmapData, bitmapData.rect,new Point(this.x,this.y),null,null,true);
}
public function lerp(v0:Number, v1:Number, t:Number):Number {
return (1-t)*v0 + t*v1;
}
However, there is noticeable stuttering appearing. In the image below, I don't clear the bitmap before drawing to it. You should be able to see that there's a lot of variation between the spacing of circles rendered, and sometimes it's extremely noticeable.
http://i.stack.imgur.com/00c39.png
I would appreciate any help at all, thanks!
I don't know if this helps but here's the code I use to fix my time step.
private var _pause :Boolean;
private var _prevTimeMS :int;
private var _simulationTime :Number;
override public function update():void
{
super.update();
if (!_pause)
{
var curTimeMS:uint = getTimer();
if (curTimeMS == _prevTimeMS)
{
return;
}
var deltaTime:Number = (curTimeMS - _prevTimeMS) / 1000;
if (deltaTime > 0.05)
{
deltaTime = 0.05;
}
_prevTimeMS = curTimeMS;
_simulationTime += deltaTime;
while (space.elapsedTime < _simulationTime)
{
// Your game step goes here.
_space.step((stage.frameRate > 0) ? (1 / stage.frameRate) : (1 / 60));
}
}
}
(Originally taken from a Nape Physics sample)
Related
I'm having trouble with AS3 which I have to use for a little research project of mine.
The AS3 project would create a number of randomly placed squares which all would rotate around its center point.
I managed to figure out how to rotate it internally using this handy walkthrough.
However I am having trouble applying this method to all squares which were created in a for loop with randomly selected stage points. Only the first created will rotate
Here is the code in question:-
for(var i=0; i<10; i++)
{
var square:Shape = new Shape();
this.addChild(square);
var posX = Math.floor(Math.random() * stage.stageWidth) + 50;
var posY = Math.floor(Math.random() * stage.stageHeight) + 50;
square.x=posX;
square.y=posY;
var curSquareAng:Number=0;
var squareRotCenter:Point=new Point(0,0);
drawShapes();
var squareMat:Matrix=square.transform.matrix.clone();
}
this.addEventListener(Event.ENTER_FRAME, onEnter);
function onEnter(e:Event):void {
curSquareAng = (curSquareAng+2)%360;
rotateSquare(curSquareAng);
}
function rotateSquare(deg:Number):void {
var mat:Matrix= squareMat.clone();
MatrixTransformer.rotateAroundInternalPoint(mat,squareRotCenter.x, squareRotCenter.y, deg);
square.transform.matrix = mat;
}
I realize I likely have to create an array for each of the squares initial center points and loop through them. However I am completely lost on how to do so. As you can likely tell I am unfamiliar with AS3 and would much appreciate any help you can give this beginner programmer. :P
You need to create your own class based on a shape, then you stuff it full of properties that represent rotation center point, current angle and whatever else you'd want your squares to contain, then give the class update method which will do what you wrote in onEnter function for itself only. Then you will have easier control over what your squares are able to do. This technique is called "encapsulation".
On a side note, if you're wanting your square to rotate around internal point of (0,0), you can set their rotation property to achieve the desired effect. For other points, the walkthrough or its equivalent should be used.
public class Square extends Shape {
public var rotationCenter:Point=new Point();
private var currentAngle:Number=0;
public var rotationSpeed:Number=2; // degrees per frame
private var baseMatrix:Matrix;
public function Square() {
// draw the shape on "this.graphics"
this.graphics.beginFill(0xffff00,1);
this.graphics.moveTo(-20,-20);
this.graphics.lineTo(20,-20);
this.graphics.lineTo(20,20);
this.graphics.lineTo(20,-20);
this.graphics.lineTo(-20,-20);
this.graphics.endFill();
// if not set in declaration, set internal vars
baseMatrix=this.transform.matrix; // likely identity matrix, but let's initialize anyway
}
public function storeMatrix():void {
// you are positioning a square after you create it, so probably you want its new location to be transformed
// that one's matrix will no longer be an identity, so go capture
baseMatrix=this.transform.matrix;
}
public function update():void {
// should be called once per frame
currentAngle=(currentAngle+rotationSpeed)%360;
var mat:Matrix= baseMatrix.clone();
MatrixTransformer.rotateAroundInternalPoint(mat,rotationCenter.x, rotationCenter.y, currentAngle);
this.transform.matrix = mat;
}
}
Now, you will have to maintain an array of squares to make them rotate separately:
var squares:Array=[];
for (var i:int=0;i<10;i++) {
var square:Square=new Square();
var posX = Math.floor(Math.random() * stage.stageWidth) + 50;
var posY = Math.floor(Math.random() * stage.stageHeight) + 50;
square.x=posX;
square.y=posY;
// after you position, give it a rotation point
square.rotationCenter.x=Math.random()*40-20;
square.rotationCenter.y=Math.random()*40-20; // -20 to 20, should do for this example
// now fix the position so your square will know that it should rotate
// its *current* transform matrix
square.storeMatrix();
// even if it's actually unchanged by changing X or Y
// also, should you desire to scale some square, you should do that prior to calling this
// now add the square to an array
squares.push(square);
}
addEventListener(Event.ENTER_FRAME,onEnter);
function onEnter(e:Event):void {
for (var i:int=0;i<squares.length;i++) squares[i].update();
// simple, isn't it? Each square will know what to do.
}
Nevermind. Thank you Vesper for putting me on the right track I've managed to solve my problem thanks to your input (not necessarily through your way but your input helped me get to the destination per say).
I think I was making it a little too complicated going though the matrix route and instead used a shape array to loop through the squares and add rotation. The solution I came to is a little simple but gets the job done.
public var rotationSpeed:Number=2; // degrees per frame
public var square:Array = new Array( );
public function Square() {
for (var i:int=0;i<10;i++) {
square[i] = new Shape();
var posX = Math.floor(Math.random() * stage.stageWidth) + 50;
var posY = Math.floor(Math.random() * stage.stageHeight) + 50;
square[i].graphics.lineStyle();
var rgb = Math.random() * 0xFFFFFF;
square[i].graphics.beginFill(rgb);
// -50 determines where the spin will center from.
square[i].graphics.drawRect(-50,-50,100,100);
square[i].graphics.endFill();
square[i].x = posX;
square[i].y = posY;
addChild(square[i]);
}
addEventListener(Event.ENTER_FRAME,onEnter);
}
private function onEnter(e:Event):void {
for (var i:int=0; i < square.length; i++) {
getChildAt(i).rotation += rotationSpeed;
}
}
I'm creating 2D platformer game. I'm having an issues with my collisions. When my character hits is standing on top of a platform it will stay there for 2.5 seconds then fall through all other platforms to the ground floor. I think it has to do something with my gravity function and collision function not working together properly. I really can't figure this out any help would be appreciated.
this = fireboy1
Here's gravity code from my character class:
public var gravity:int = 0;
public var floor:int = 461;
public function adjust():void
{
//applying gravity
this.y += gravity;
if(this.y + this.height <floor)
gravity++;
else
{
gravity = 0;
this.y = floor - this.height;
}
and here is the code for my collisions from the main class:
//collision detection of platform1
public function platform1Collision():void
{
if(fireboy1.hitTestObject(Platform1))
{
if(fireboy1.y > Platform1.y)
{
fireboy1.y = Platform1.y + Platform1.height;
}
else
{
fireboy1.y = Platform1.y - fireboy1.height;
}
}
Your issue is likely that the y position is being incremented every frame (regardless of any collisions)
A better approach, would be to create one game loop / Enter Frame handler, instead of having one for the player, one for each platform, etc. You also had some incorrect math with calculating players position relative to the platform and floor.
public var gravity:int = 0;
public var floor:int = 461;
//add this in your constructor or init of the class
this.addEventListener(Event.ENTER_FRAME, gameLoop);
function gameLoop(e:Event):void {
//start with all your platform collisions
if(fireboy1.hitTestObject(Platform1))
{
if(jumping){ //some flag that indicates the player is jumping at the moment
//place fireboy right under the platform
fireboy1.y = Platform1.y + Platform1.height;
}else{
//place fireboy on the platform perfectly
fireboy1.y = (Platform1.y + Platform1.height) - fireboy1.height; //changed your math here
return; //there's no use checking any other platforms or doing the gravity increment, since the player is already on a platform, so exit this method now.
}
}
//any other platform checks (should be done in a loop for sanity)
//if we made it this far (no returns), then do the gravity adjustments
fireboy1.y += gravity;
if(fireboy1.y - fireboy1.height < floor){ //changed your math here
gravity++;
} else
{
gravity = 0;
fireboy1.y = floor - fireboy1.height;
}
}
Im trying to random an object(2) every time object(1).y is beyond the stage. but the problem comes that its constantly moving and sometimes its "jumps" that position where im looking to make the change.
i did try with the "if (road1.y >= stage.stageHeight) {" but it doesnt trigger.
And when I'm doing it the the speed of the movement it triggers only when it has been on the stage 2 times before.
the registration point of all MovieClips are in the TOP LEFT.
the code is this
private var road1,road4:Road1;
private var road2:Road2;
private var road3:Road3;
private var randomRoad:Sprite = new Sprite();
private var offset:Number = 0;
private var counter:Number = 0;
public function onAdded(e:Event):void {
removeEventListener(Event.ADDED_TO_STAGE,onAdded);
addChild(road1=new Road1());
addChild(randomRoad);
addChild(road4=new Road1());
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
public function onEnterFrame(e:Event):void {
if (startRandom == true) {
if (Math.random() > 0.5) {
randomRoad.addChild(road2 =new Road2());
} else {
randomRoad.addChild(road3 =new Road3());
}
startRandom = false;
trace(randomRoad);
trace(startRandom);
}
if (road1.y >= stage.stageHeight) {
startRandom = true;
trace("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz");
}
offset += speed;
counter++;
road1.y = offset % 800 - 800;
randomRoad.y = road1.y + road1.height;
road4.y = randomRoad.y + randomRoad.height;
}
Try this:
if (road1.y >= -speed) {
startRandom = true;
trace("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz");
}
What is the height of the stage? 800? Maybe the problem is in that road1.y will never go greater than -1;
Wouldn't it be more logical to base your spawning of an object on distance traveled? Then it wouldn't be reliant on determining the y position of a given road piece in relation to the screen.
I am going to assume you have a car in this game, since there is road. Give the car a distance variable and increment that variable based on the speed.
distance += speed;
if (distance > 400)
{
spawnObject();
distance = 0; // reset the distance traveled.
}
EDIT :
I think I may have misunderstood what you were trying to do as I thought you were trying to spawn objects on the side of the road and this was a method of determining when to spawn them. In re-reading it, it seems like the 'object' you are trying to spawn is the next road piece itself. Would have been better to just use the word road in your question as opposed to 'object(2)'
I have implemented a gameloop in Flash/Actionscript/Starling and I want to throw it at you to see if this is a valid implementation.
I wanted to have a variable time step approach.
private var _deltaTime:Number = 0;
private var _lastTime:Number = 0;
private var _speed = 1000 / 40;
private function onEnterFrame() {
var now = new Date().getTime();
var delta = now - _lastTime;
_deltaTime += delta - _speed;
_lastTime = now;
//skip if frame rate to fast
if (_deltaTime <= -_speed) {
_deltaTime += _speed;
return;
}
update();
}
private function update() {
updateGameState();
if (_deltaTime >= _speed) {
_deltaTime -= _speed;
update();
}
}
What I got sofar is that I have a constant speed (more or less).
My question is is there a better approach so that the movements will appear even
smoother.
What is really surprising to me is that even thou the FPS is pretty much constant (60FPS)
the movement is sometimes bumpy yet smoother than with the naive gameloop.
Youre on the right track - assuming that onEnterFrame is triggered in some way by Event.ENTER_FRAME - instead of skipping update, call it on every frame but pass in the time elapsed:
private function onEnterFrame() {
var now = new Date().getTime();
var delta = now - _lastTime;
_lastTime = now;
updateGameState(delta/1000);//divide by 1000 to give time in seconds
}
In updateGameState, you can utilise 'delta' to calculate movement etc, eg:
function updateGameState(timeElapsed:Number):void {
myAlien.x += myAlienSpeedPerSecond*timeElapsed;
}
This way you get smooth movement even when frame rate varies.
from the Starling introduction pages, it shows that time elapsed is built into the EnterFrameEvent class.
// the corresponding event listener
private function onEnterFrame(event:EnterFrameEvent):void
{
trace("Time passed since last frame: " + event.passedTime);
enemy.moveBy(event.passedTime * enemy.velocity);
}
http://wiki.starling-framework.org/manual/animation#enterframeevent
we are sending from server to client the coords of the ball each 300 ms. We have to interpolate coords to make the moving smooth. Here is the code (AS3):
private function run(event:Event):void
{
// Current frame ball position
var currentPosition:Point = new Point(this.x, this.y);
// Vector of the speed
_velocity = _destinationPoint.subtract(currentPosition);
// Interpolation
// Game.timeLapse - time from last package with coordinates (last change of destinationPoint)
// stage.frameRate - fps
_velocity.normalize(_velocity.length * 1000 / Game.timeLapse / stage.frameRate);
// If ball isn't at the end of the path, move it
if (Point.distance(currentPosition, _destinationPoint) > 1) {
this.x += _velocity.x;
this.y += _velocity.y;
} else {
// Otherwise (we are at the end of the path - remove listener from this event
this.removeEventListener(Event.ENTER_FRAME, run);
this.dispatchEvent(new GameEvent(GameEvent.PLAYER_STOP));
}
}
The problem is described in the following picture:
Red point - destination point
Black lines - lines from curret point to destination without
normalization
Green dotted - the path of the ball
Maybe there is a way to make moving smooth but more accurate?
If you want to interpolate path steps for exactly three points, you need to use quadratic Bezier curve math to be able to calculate any position on the curve for any given distance from its starting point. You need this to get equal steps along the curve, that you have on your picture. That's rather tricky, because when you use bezier curve equiations in polynomial form, you don't get equal distance along the curve for equal parameter deltas.So, you need to treat bezier curve as a parabola segment (which it effectively is), and the task can be reformulated as "stepping along a parabolic curve with steps of equal length". This is still quite tricky, but fortunately there is a solution out there: http://code.google.com/p/bezier/
I used this library several times (to make equal steps along a parabolic curve) and it worked perfectly well for me.
Most likely you would want to interpolate between arbitrary set of points. If this is the case, you may use Lagrange approximation.Below is my simple implementation of Lagrange approximation. (Googling for it will certainly give you more.) You supply approximator with arbitrary number of known function values and it can generate the value of a smooth function for any value of the argument in between.
--
package org.noregret.math
{
import flash.geom.Point;
import flash.utils.Dictionary;
/**
* #author Michael "Nox Noctis" Antipin
*/
public class LagrangeApproximator {
private const points:Vector.<Point> = new Vector.<Point>();
private const pointByArg:Dictionary = new Dictionary();
private var isSorted:Boolean;
public function LagrangeApproximator()
{
}
public function addValue(argument:Number, value:Number):void
{
var point:Point;
if (pointByArg[argument] != null) {
trace("LagrangeApproximator.addValue("+arguments+"): ERROR duplicate function argument!");
point = pointByArg[argument];
} else {
point = new Point();
points.push(point);
pointByArg[argument] = point;
}
point.x = argument;
point.y = value;
isSorted = false;
}
public function getApproximationValue(argument:Number):Number
{
if (!isSorted) {
isSorted = true;
points.sort(sortByArgument);
}
var listLength:uint = points.length;
var point1:Point, point2:Point;
var result:Number = 0;
var coefficient:Number;
for(var i:uint =0; i<listLength; i++) {
coefficient = 1;
point1 = points[i];
for(var j:uint = 0; j<listLength; j++) {
if (i != j) {
point2 = points[j];
coefficient *= (argument-point2.x) / (point1.x-point2.x);
}
}
result += point1.y * coefficient;
}
return result;
}
private function sortByArgument(a:Point, b:Point):int
{
if (a.x < b.x) {
return -1;
}
if (a.x > b.x) {
return 1;
}
return 0;
}
public function get length():int
{
return points.length;
}
public function clear():void
{
points.length = 0;
var key:*;
for (key in pointByArg) {
delete pointByArg[key];
}
}
}
}
You could send more than one coordinate each tick. Or send some extra properties along with each point, maybe to say if it is a point where the ball bounces, or if it can be smoothed.
Sending a series of points in one transaction would give you greater accuracy, and wont add too much to the packet size, compared to the overhead of sending, processing, and receiving.