Pairing a draggable object to a target object in AS3 - actionscript-3

I'm currently stuck with my approach below. I'm not entirely sure if using "hitTestObject" method is appropriate in pairing the pieces to their respective place. I was able to at least match the chess piece to their respective location (that's the best I can do and I feel i'm doing it wrong) but I'm now stuck in counting how many pieces are actually in their correct places. e.g. when I move the pawn to a different tile, it will still count as one, I also want to avoid duplicate counting, example, If pawn is already in the correct location, it will just count as 1, and if it was moved, then that count will be removed. Only count the pieces that are in the correct tile.
My goal here is to be able to make all the chess pieces draggable and determine if they're in their respective location. If ALL the chess pieces are in their location, it will trace or call a function.
Thank you!
import flash.events.Event;
import flash.display.MovieClip;
import flash.events.MouseEvent;
/* Declaring an X and Y variable to be used as a reset container */
var xPos: int, yPos: int;
/* Attaching event listeners for each chess piece */
addListeners(
king, queen, bishop_1, bishop_2, knight_1, knight_2, rook_1, rook_2,
pawn_1, pawn_2, pawn_3, pawn_4, pawn_5, pawn_6, pawn_7, pawn_8);
/* Getting the original x and y postion to be used as a reset */
function getPosition(currentTarget: Object): void {
xPos = currentTarget.x;
yPos = currentTarget.y;
}
/* Function to get the suffix value of an object. example, I need to get the value 4 from "pawn_4" */
function getLastCharInString($s: String, $pos: Number): String {
return $s.substr($s.length - $pos, $s.length);
}
/* A simple function that rotates the chess piece */
function lift(object: Object, rot: Number) {
object.rotation = rot;
}
function dragObject(e: MouseEvent): void {
getPosition(e.currentTarget);
lift(e.currentTarget, -10);
getChildByName(e.currentTarget.name + "_hs").alpha = 1;
e.currentTarget.startDrag();
}
/* This variable is supposed to hold the value of each piece that is correctly placed in each tile.
The total score should be 16 as there are 16 pieces. Only correcly placed piece should be added in the total score. */
var counter:int;
function stopDragObject(e: MouseEvent): void {
var curretTarget = e.currentTarget.name;
lift(e.currentTarget, 0);
/* Hide active hotspots */
getChildByName(e.currentTarget.name + "_hs").alpha = 0;
var multiplePieceSufix = Number(getLastCharInString(curretTarget, 1));
if (multiplePieceSufix >= 1) {
/* Boolean variables that checks whether the current piece is active*/
var isPawn: Boolean = false,
isBishop: Boolean = false,
isKnight: Boolean = false,
isRook: Boolean = false,
currentTargeName;
var widthDiff = getChildByName(e.currentTarget.name + "_hs").width - getChildByName(e.currentTarget.name).width / 2;
var heightDiff = getChildByName(e.currentTarget.name + "_hs").height - getChildByName(e.currentTarget.name).height / 2;
if (curretTarget.substr(0, 4) == "pawn") {
isPawn = true;
} else if (curretTarget.substr(0, 6) == "bishop") {
isBishop = true;
} else if (curretTarget.substr(0, 6) == "knight") {
isKnight = true;
} else if (curretTarget.substr(0, 4) == "rook") {
isRook = true;
}
if (isPawn == true) {
/* there are total of 8 pieces of pawn */
for (var w = 1; w < 9; w++) {
currentTargeName = this["pawn_" + w + "_hs"];
if (e.target.hitTestObject(currentTargeName)) {
/* For some reason the chess pieces are not aligning with their "_hs" version, I already checked their registry point and it seem to be normal.
so to fix, I had to manually add some hard coded values to adjust their location. */
e.currentTarget.x = currentTargeName.x - 8;
e.currentTarget.y = currentTargeName.y + currentTargeName.height;
}
}
} else if (isBishop == true) {
for (var x = 1; x < 3; x++) {
currentTargeName = this["bishop_" + x + "_hs"];
if (e.target.hitTestObject(currentTargeName)) {
e.currentTarget.x = currentTargeName.x - 9;
e.currentTarget.y = currentTargeName.y + currentTargeName.height - 18;
}
}
} else if (isKnight == true) {
for (var y = 1; y < 3; y++) {
currentTargeName = this["knight_" + y + "_hs"];
if (e.target.hitTestObject(currentTargeName)) {
e.currentTarget.x = currentTargeName.x - 8;
e.currentTarget.y = currentTargeName.y + currentTargeName.height;
}
}
} else if (isRook == true) {
for (var z = 1; z < 3; z++) {
currentTargeName = this["rook_" + z + "_hs"];
if (e.target.hitTestObject(currentTargeName)) {
e.currentTarget.x = currentTargeName.x - 8;
e.currentTarget.y = currentTargeName.y + 62;
}
}
}
} else {
if (e.target.hitTestObject(getChildByName(e.currentTarget.name + "_hs"))) {
/* Again, I'm not sure why the pieces are not aligning as intended.
modX and modY is a holder for the adjustment value. I'm not comfortable
seeing this approach myself, but I also run out of ideas how to fix it. */
var modX: Number, modY: Number;
if (e.currentTarget.name == "king") {
modX = 11;
modY = 53;
} else {
modX = 11;
modY = 29;
}
e.currentTarget.x = getChildByName(e.currentTarget.name + "_hs").x - modX;
e.currentTarget.y = getChildByName(e.currentTarget.name + "_hs").y + getChildByName(e.currentTarget.name + "_hs").height - modY;
}
}
/* This is supposed to add to the total score or count of how many pieces are placed correctly.
Thie problem with thi scounter, as it also counts any piece that is places to any "_hs" */
counter++;
trace(counter);
e.currentTarget.stopDrag();
}
function addListeners(...objects): void {
for (var i: int = 0; i < objects.length; i++) {
objects[i].addEventListener(MouseEvent.MOUSE_DOWN, dragObject);
objects[i].addEventListener(MouseEvent.MOUSE_UP, stopDragObject);
// hide hotspots
getChildByName( objects[i].name + "_hs" ).alpha = 0;
}
}
Source: Download the FLA here
--
Updates:
I have added comments in my code to clarify what I'm trying to accomplish.
I'm planning to do board game in flash which has similar function and behaviour to this. User can drag the object to a specified tile and check wether that object belongs there or not.

After reviewing your code, your question is quite broad. I'm going pair it down to what seems to be your main concern - the score / counting correctly moved pieces.
Right now, you do the following every time an object is dragged:
counter++;
This means that the counter will increment no matter where you drag the object, and no matter how times you drag the object. (so even if the piece was already in the correct spot, if you dragged it a second time it will still increment your counter).
What you need to do, is associate a flag with each object to indicate whether it is in the correct location or not, and set that flag to the appropriate value every time that object is done dragging.
Something like this:
//don't use target, use currentTarget
if (e.currentTarget.hitTestObject(currentTargeName)) {
e.currentTarget.correct = true; //since MovieClips are dynamic, you can just make up a property on them and assign a value to it.
//to fix your alignment:
e.currentTarget.x = currentTargeName.x + ((currentTargetName.width - e.currentTarget.width) * 0.5);
e.currentTarget.y = currentTargeName.y + currentTargeName.height;
}else{
//if the hit test is false, mark it as NOT correct
e.currentTarget.correct = false;
}
Then, later to know the current count, iterate over all the pieces and check their correct value. This would be much easier if all your pieces were in an array.
var allPieces:Array = [king, queen, bishop_1, bishop_2, knight_1, knight_2, rook_1, rook_2,
pawn_1, pawn_2, pawn_3, pawn_4, pawn_5, pawn_6, pawn_7, pawn_8];
function countCorrect():Boolean {
var ctr:int = 0;
for(var i:int=0;i<allPieces.length;i++){
if(allPieces[i].correct) ctr++;
}
return ctr;
}
trace(countCorrect() + " of " allPieces.length " are correct");
As an aside, this best way to do this would be with some custom class files. That would however require a complete refactoring of your code.
Also, you probably don't want to use hitTestObject, as even if a piece is mostly over a neighbor, it will still be true as long as 1 pixel of it's bound touch 1 pixel of the tile. Better would be to do a hitTestPoint on the tile, and pass in the center point of the piece (the the middle of the piece has to be touching the tile for it to count).
//a point that is the center of the events current target (the piece)
var point:Point = new Point();
point.x = e.currentTarget.x + (e.currentTarget.width * 0.5);
point.y = e.currentTarget.y - (e.currentTarget.height * 0.5);
if (currentTargetName.hitTestPoint(point)) {

Related

AS3 How can I get back the object that being dragged to Item

I have board with 16 objects(MovieClip), every one of them have a unique instance name (slot1-16).
I'm trying to make a drag and drop code that return the instance name of the object dragged on it.
function fl_ReleaseToDrop(evt:MouseEvent):void {
var object = evt.currentTarget;
if(object is textBox || object is UILoader)
{
for(var i:int = 1; i < 16; i++){
//Checks the correct drop target
if (object.hitTestObject(getChildByName("slot" + i)))
{
trace("slot" + i);
slot(getChildByName("slot" + i)).gotoAndStop(3);
}else{
object.x = xPos; //If not, return the clip to its original position
object.y = yPos;
}
}
object.stopDrag();
}
}
What really happened is that the only place that i can drag in to is slot1, other slots are not working.
In your current code, if the 1st hit test with slot1 object fails, you return your dragged object to its original position and of course all the hit tests with other slots will fail.
So you should return your object to its original position just after doing the hit test with all objects. You can use, for example, a Boolean var to know if there is at least one succeeded hit test, in that case you don't need to return your dragged object to its original position :
function fl_ReleaseToDrop(evt:MouseEvent):void
{
var object = evt.currentTarget;
var hit_test:Boolean = false;
if(object is textBox || object is UILoader)
{
for(var i:int = 1; i < 16; i++)
{
if (object.hitTestObject(getChildByName("slot" + i)))
{
hit_test = true;
slot(getChildByName("slot" + i)).gotoAndStop(3);
}
}
if(!hit_test)
{
object.x = xPos;
object.y = yPos;
}
object.stopDrag();
}
}
Hope that can help.

as3 Quadtree being slow

This is my quadtree class, but i haven't added the collision detection yet, in all the examples online they can get 500 + at 60 fps with collision detection but my one only running at 20 fps without collision detection.
I'm following this tutorial http://gamedevelopment.tutsplus.com/tutorials/quick-tip-use-quadtrees-to-detect-likely-collisions-in-2d-space--gamedev-374 which is java but im using as3
public class Quadtree extends Entity
{
private var Max_objects:int = 1;
private var Max_levels:int = 5;
private var level:int;
private var objects:Vector.<Rectangle>;
public var rectangle:Rectangle;
public var Quadtree_list:Vector.<Quadtree>;
public function Quadtree(tmp_level:int , tmp_rec:Rectangle)
{
level = tmp_level;
objects = new Vector.<Rectangle>();
rectangle = tmp_rec;
Quadtree_list = new Vector.<Quadtree>();
Quadtree_list.length = 3;
}
public function clear():void
{
objects.length = 0;
for (var i:Number = 0; i < Quadtree_list.length ; i++)
{
if (Quadtree_list[i] != null)
{
Quadtree_list[i].clear();
world.remove(Quadtree_list[i]);
Quadtree_list[i] = null;
}
}
}
public function split():void
{
var subWidth:int = rectangle.width / 2;
var subHeight:int = rectangle.height / 2;
var xx:int = rectangle.x;
var yy:int = rectangle.y;
Base._world.add(Quadtree_list[0] = new Quadtree(level + 1, new Rectangle(xx + subWidth, yy, subWidth, subHeight)));
Base._world.add(Quadtree_list[1] = new Quadtree(level+1,new Rectangle(xx ,yy,subWidth,subHeight)));
Base._world.add(Quadtree_list[2] = new Quadtree(level+1,new Rectangle(xx,yy + subHeight,subWidth,subHeight)));
Base._world.add(Quadtree_list[3] = new Quadtree(level+1,new Rectangle(xx + subWidth,yy + subHeight,subWidth,subHeight)));
}
/*
* Determine which node the object belongs to. -1 means
* object cannot completely fit within a child node and is part
* of the parent node
*/
public function get_index(tmp_rect:Rectangle):Number
{
var index:int = -1;
var verticalMidpoint:Number = rectangle.x + (rectangle.width / 2);
var horizontalMidpoint:Number = rectangle.y + (rectangle.height / 2);
// Object can completely fit within the top quadrants
var topQuadrant:Boolean = (tmp_rect.y < horizontalMidpoint && tmp_rect.y + tmp_rect.height < horizontalMidpoint);
// Object can completely fit within the bottom quadrants
var bottomQuadrant:Boolean = (tmp_rect.y > horizontalMidpoint);
// Object can completely fit within the left quadrants
if (tmp_rect.x < verticalMidpoint && tmp_rect.x + tmp_rect.width < verticalMidpoint)
{
if (topQuadrant)
{
index = 1;
}
else if (bottomQuadrant)
{
index = 2;
}
}
else
// Object can completely fit within the right quadrants
if (tmp_rect.x > verticalMidpoint)
{
if (topQuadrant)
{
index = 0;
}
else if (bottomQuadrant)
{
index = 3;
}
}
return index;
}
/*
* Insert the object into the quadtree. If the node
* exceeds the capacity, it will split and add all
* objects to their corresponding nodes.
*/
public function insert(tmp_rect:Rectangle):void
{
if (Quadtree_list[0] != null)
{
var index:int = get_index(tmp_rect);
if (index != -1)
{
Quadtree_list[index].insert(tmp_rect)
return;
}
}
objects.push(tmp_rect);
if (objects.length > Max_objects && level < Max_levels)
{
if (Quadtree_list[0] == null)
{
split();
}
var i:int = 0;
while (i < objects.length)
{
var indexx:int = get_index(objects[i]);
if (indexx != -1)
{
Quadtree_list[indexx].insert(objects[i]);
objects.splice(i, 1);
}
else
{
i++;
}
}
}
}
Can you see why it's not performing very well?
Hard to say for certain without seeing exactly how you're using it. Plus it extends Entity which could be doing ...anything :)
I'm not an expert on Quadtrees either, but if you're calling split() a lot, it looks like it could end up being taxing - lots of instantiation calls to new Quadtree and new Rectangle. If this is indeed a bottleneck, you could look into instantiating one rectangle instance that you just pass around. Same with Quadtree. Or use object pooling so you're at least recycling instead of creating new things like crazy.
I hope that helps :)

How to rearranged items in an inventory

I'm creating a game in AS3.
The player can grabb items and add it to his inventory in a line.
Everything is working, but I've got a bug when the player use an item wich is in the middle of the line.
It doesen't rearanged well..
(here is a video if I'm not very clear : http://ul.to/z7su5dqm or here https://drive.google.com/file/d/0B5-MjJcEPm3lTTlDV09MYWxMOFE/edit?usp=sharing)
I've got the code that add the item to the inventory :
public function addInvItem(itemName:String):void{
var itemRef:Object = getDefinitionByName(itemName.toLowerCase()+"Inv");
var addedItem:MovieClip = new itemRef;
addedItem.displayName = itemName;
if (playerItems.length < 8){ // This is for the top row of up to 4 items
addedItem.y = 520;
addedItem.x = 60 + (playerItems.length) * 100;
}
if (isUnique(addedItem)){
this.addChild(addedItem);
playerItems.push(addedItem);
allItems.push(addedItem);
addedItem.buttonMode = true;
addedItem.invItem = true;
addedItem.addEventListener(MouseEvent.CLICK, useItem, false, 0, true);
puzzle = Engine.puzzle;
puzzle.gotItem(addedItem.displayName);
}
So the first item is add at x= 60 and y = 520.
And then I've got this code in order to remove and rearranged the items :
public function removeInvItem(itemName:String):void{
removedItem = itemName;
var itemNum:int;
for (var i in playerItems){
if (playerItems[i].displayName == itemName){
playerItems[i].visible = false;
itemNum = i;
} else {
playerItems[i].visible = true;
}
}
playerItems = playerItems.filter(checkForItem);
// Rearrange the rest of the items
for (i in playerItems){
if (i >= itemNum){
playerItems[i].x -= 100;
}
}
}
Do you see where could be the error that push my first item ? (I suppose it came from playerItems[i].x -= 100).
I must find a way to tell the code that first item can't be less than x = 60 but the other must move x= -100 everytime their are used...
Any idea how I can do that ?
Thank you very much,
You are correct in assuming playerItems[i].x -= 100; is where the problem is caused. You are subtracting the current x position without any checks for if that falls over your inventory icon asset.
You could do something like this instead:
public function removeInvItem(itemName:String):void{
removedItem = itemName;
var itemNum:int;
for (var i in playerItems){
if (playerItems[i].displayName == itemName){
playerItems[i].visible = false;
itemNum = i;
} else {
playerItems[i].visible = true;
}
}
adjustInventory( itemNum );
}
public function adjustInventory( itemNum:int ):void {
var i:int;
for ( i=itemNum; i < playerItems.length; i++ ) {
//you can replace 60 with inventoryIcon.x + inventoryIcon.width instead
playerItems[i].x -= playerItems[i].x - 100 >= 60 ? 100 : playerItems[i].x - 60;
}
}
This evaluates the distance you are about to move before you do it, and only moves the necessary inventory items. I haven't tested this code but this should put you on the right path.

Contiguous Block Plotting/Removal Logic

I've been attempting to write some logic for a program I have been working on and have run into some difficulty.
Essentially what I'm creating on my stage programmatically is a 4x4 grid (16 blocks), which looks just like this:
The user's task is to plot a contiguous shape onto the grid by clicking on the blocks and their shape should feature no gaps and no diagonally plotted blocks, for example the following would be a legal shape:
However, the following shape wouldn't be and would throw out an error to the user in the form of a pop-up graphic:
The plotting process for the grid is associated with a 4x4 virtual representation of the grid in Boolean Array form and looks like this:
public static var ppnRowArray1:Array = [false,false,false,false];
public static var ppnRowArray2:Array = [false,false,false,false];
public static var ppnRowArray3:Array = [false,false,false,false];
public static var ppnRowArray4:Array = [false,false,false,false];
public static var ppnColumnArray:Array = [ppnRowArray1,ppnRowArray2,ppnRowArray3,ppnRowArray4];
As the user clicks and selects a block, changing the colour property to brown, the relevant boolean property in my 'virtual grid representation' array will change from false to true. If a plot is illegally made then this property is changed back to false and the user is then invited to try their next plot again.
I have managed to write the code which forces the user to plot a legal shape and works out when an illegal plot has been made, but I now need to write the logic for when a user de-selects a block from an existing legal shape, making it non-contiguous and this is where my problem lies.
Here is the working solution as it stands.
//---------------------------------------------------------------------
public static function ppnCountSetCells():int
{
//Count Each 4x4 Grid Cell
var count:int = 0;
for (var row=0; row<=3; row++)
{
for (var col=0; col<=3; col++)
{
if (ppnColumnArray[col][row])
{
count++;
}
}
}
return count;
}
//---------------------------------------------------------------------
public static function ppnBlockValid():Boolean
{
if (ppnCountSetCells() > 1)
{
for (var row=0; row<=3; row++)
{
for (var col=0; col<=3; col++)
{
if (ppnColumnArray[col][row] == true)
{
// Check if we are connected to another set square
var validNeighbours:int = 0;
// Check North
if (row > 0)
{
if (ppnColumnArray[col][row - 1] == true)
{
validNeighbours++;
}
}
// Check South
if (row < 3)
{
if (ppnColumnArray[col][row + 1] == true)
{
validNeighbours++;
}
}
// Check West
if (col > 0)
{
if (ppnColumnArray[col - 1][row] == true)
{
validNeighbours++;
}
}
//-----------------------------------------------------------------------------------------
// Check East
if (col < 3)
{
if (ppnColumnArray[col + 1][row] == true)
{
validNeighbours++;
}
}
//-----------------------------------------------------------------------
if (validNeighbours < 1)
{
return false;
}
//-----------------------------------------------------------------------
}
}
}
}
return true;
}
//---------------------------------------------------------------------
function addBlock(e:MouseEvent):void
{
//trace("You Have Clicked On Grid Block Number: " + e.currentTarget.id);
if (InterfaceButtons.panelOpen == false)
{
//Listen to see if the block click is adjoining and pass back to see if it is valid on the grid
var col:int = (e.currentTarget.id - 1) % 4;
var row:int = (e.currentTarget.id - 1) / 4;
ppnColumnArray[col][row] = true;
addOrRemove = "add";
ppnBlockValid();
//Get the Block Valid Result (True or False) and pass it into a Boolean variable to use later
ppnGridError = ppnBlockValid();
trace("Is This Valid? " + ppnBlockValid());
//----------------------------------------------------------------------------------------------
//Push Blocks Selected into Array
ppnShapeArray[e.currentTarget.id] = true;
trace(ppnShapeArray);
//----------------------------------------------------------------------------------------------
//Add 1 to the block count which directly effects the final outcome depending on ++ or --
ppnBlocksSelected++;
PlantPopNitDesignPlot.ppnPlotMade = false;
//Hide Block to Reveal Brown One
e.currentTarget.alpha = 0;
//-----------------------------------------------------------------------------------------------
//Output an error if one is present on Click based on gridError Boolean Variable
ppnOutputAddError();
if (ppnGridError == false)
{
//Restore the block's alpha property as it isn't allowed to be selected, removing counter by one -- and changing final output accordingly
e.currentTarget.alpha = 1;
ppnBlocksSelected--;
ppnColumnArray[col][row] = false;
ppnShapeArray[e.currentTarget.id] = false;
ppnPopulateTotalSiteUnitsTxt();
}
//Update final total
ppnPopulateTotalSiteUnitsTxt();
//Call again to do dynamic colour font change should total exceed 10
ppnPopulateTotalSiteUnitsTxt();
//Added in to make sure it executes every time if an error is made.
if (ppnGridError == true)
{
e.currentTarget.removeEventListener(MouseEvent.CLICK, addBlock);
e.currentTarget.addEventListener(MouseEvent.CLICK, removeBlock);
}
}
}
function removeBlock(e:MouseEvent):void
{
if (InterfaceButtons.panelOpen == false)
{
var col:int = (e.currentTarget.id - 1) % 4;
var row:int = (e.currentTarget.id - 1) / 4;
ppnColumnArray[col][row] = false;
addOrRemove = "remove";
ppnBlockValid();
ppnGridError = ppnBlockValid();
trace("Is This Removal Valid? " + ppnBlockValid());
//trace("You Have Clicked On Grid Block Number: " + e.currentTarget.id);
e.currentTarget.alpha = 1;
ppnShapeArray[e.currentTarget.id] = false;
//trace("ppnShapeArray - " + ppnShapeArray);
//---------------------------------------------------------------------
ppnBlocksSelected--;
PlantPopNitDesignPlot.ppnPlotMade = false;
//Output an error if one is present on Click based on gridError Boolean Variable
ppnOutputRemoveError();
if (ppnGridError == false)
{
//Restore the block's alpha property as it isn't allowed to be selected, removing counter by one -- and changing final output accordingly
e.currentTarget.alpha = 0;
ppnBlocksSelected--;
ppnColumnArray[col][row] = true;
ppnShapeArray[e.currentTarget.id] = true;
ppnPopulateTotalSiteUnitsTxt();
}
//Update Final Total
ppnPopulateTotalSiteUnitsTxt();
//Call again to do dynamic colour font change should total falls below 10
ppnPopulateTotalSiteUnitsTxt();
//Added in to make sure it executes every time.
if (ppnGridError == true)
{
e.currentTarget.addEventListener(MouseEvent.CLICK, addBlock);
e.currentTarget.removeEventListener(MouseEvent.CLICK, removeBlock);
}
}
}
}
}
//---------------------------------------------------------------------
Now, for most occurences this logic works and detects illegal plots when adding or removing a block to the shape, however recently I discovered that when I have 5 > blocks in the shape, the logic for detecting an error on removal fails in certain circumstances.
A few examples of shapes being declared true and legal when they are not (when a block has been removed) are as follows:
I can see that it is the logic written in my 'ppnBlockValid():Boolean' function that needs adjusting to compensate for these outputs. It seems you can only remove a block providing that the neighbouring blocks are still joined to something else. While this works for smaller shapes, larger shapes (e.g. 5 blocks or more) can theoretically be split down the middle, so I think the code needs adjusting to account for this.
But how? Any help on this would be greatly appreciated.
Many thanks in advance and if you need any further information from me please let me know.
Cheers,
Joel
Edited
Thank you very much for providing that enhanced code and explanation #dhc I really appreciate it, but I'm still a little confused about how to implement all this properly.
Here is my current 'ppnBlockValid' function code based on your suggestion below:
public static function ppnBlockValid():Boolean
{
ppnIslands = [];
ppnAddNewIsland = [];
ppnAddToExistingIsland = [];
if (ppnCountSetCells() > 1)
{
for (var row=0; row<=3; row++)
{
for (var col=0; col<=3; col++)
{
if (ppnColumnArray[col][row] == true)
{
var addedToIsland = false;
// Check if we are connected to another set square
var validNeighbours:int = 0;
// Check North
if (row > 0)
{
if (ppnColumnArray[col][row - 1] == true)
{
validNeighbours++;
}
//----------------------------------
//ISLAND CHECK
if (ppnColumnArray[col][row - 1])
{
ppnAddToExistingIsland.push([col,row - 1],[col,row]);
addedToIsland = true;
}
}
// Check South
if (row < 3)
{
if (ppnColumnArray[col][row + 1] == true)
{
validNeighbours++;
}
}
// Check West
if (col > 0)
{
if (ppnColumnArray[col - 1][row] == true)
{
validNeighbours++;
}
//----------------------------------
//ISLAND CHECK
if (ppnColumnArray[col - 1][row])
{
ppnAddToExistingIsland.push([col - 1,row],[col,row]);
addedToIsland = true;
}
}
//-----------------------------------------------------------------------------------------
// Check East
if (col < 3)
{
if (ppnColumnArray[col + 1][row] == true)
{
validNeighbours++;
}
}
//-----------------------------------------------------------------------
if (! addedToIsland)
{
ppnIslands.push([col,row]);
}
//-----------------------------------------------------------------------
if (ppnIslands.length >= 2 && addOrRemove == "remove")
{
trace("TWO ISLANDS HAVE BEEN FORMED AND AN ERROR SHOULD BE OUTPUT");
validNeighbours--;
}
/**/
//return (ppnIslands.length<=1);// 0 islands is valid also!
//-----------------------------------------------------------------------
if (validNeighbours < 1)
{
return false;
}
//-----------------------------------------------------------------------
}
}
}
}
return true;
}
//---------------------------------------------------------------------
I have been using the following shape as my experiment with the code:
Based on the above code and example shape, my current trace output is:
ppnIsland = |0,0|,0,2
ppnAddNewIsland =
ppnAddToExistingIsland = 0,0, 1,0, 1,0, 2,0, 2,0, 3,0, 1,0, 1,1, 1,1, 1,2, 0,2, 1,2, 1,2, 2,2, 2,2, 3,2
It appears that despite the shape being contiguous, the code I've tried interpreting from you is finding an additional island, in this case 'Col: 0, Row: 2', before a block removal has even taken place? Is this right?
This code of course does output an error if I try to remove the middle (red) block as the 'ppnIsland' array contains > 1 island, however I don't think its detecting the correct output?
Do I need to cross-reference the 'ppnIsland' and 'ppnAddToExistingIsland' arrays using the indexOf command to check if either element is part of an existing island?
Joel
You could track "islands" as separate lists as you process (in ppnBlockValid). So, for instance (caps are selected tiles):
a B c d
e F g H
i J k L
m n o p
When you process row one, you create an island for B. When you process row 2, you find B connected to F, so the island becomes [B,F]. Then, when you encounter H, which is not connected, you have two islands: [B,F],[H]. On row 3, your islands would look like [B,F,J],[H,L]. If K were selected, you'd find J and L connected, and you'd consolidate them into one island. You only have to check the current row for connections between islands, so you'd only have to check J and L in this case.
If you end up with one island, you're fine, otherwise not. This is, unfortunately, an order n**2 search, but your grid is small so it probably doesn't mean much.
Something like this (WARNING: not debugged code):
private var islands : Array;
public function ppnBlockValid():Boolean {
islands = [];
if (ppnCountSetCells() > 1) {
for (var row=0; row<=3; row++) {
for (var col=0; col<=3; col++) {
if (ppnColumnArray[col][row]) {
var addedToIsland = false;
// Check if we are connected to another set square
// Check North
if (row > 0) {
if (ppnColumnArray[col][row-1]) {
addToExistingIsland([col,row-1],[col,row]);
addedToIsland = true;
}
}
// Check South - not needed since next row will catch this on North
// Check West
if (col > 0) {
if (ppnColumnArray[col-1][row]) {
addToExistingIsland([col-1,row],[col,row]);
addedToIsland = true;
}
}
// Check East - not needed since next col will catch this on West
if (!addedToIsland) { addNewIsland([col,row]); }
}
}
}
}
return (islands.length<=1); // 0 islands is valid also!
}
You just have to implement "addNewIsland" and "addToExistingIsland". addNewIsland is easy: just add a new element to the islands array. You may want to use a string ID for the tiles instead of an array (e.g. "01" instead of [0,1]) since it may make checking for existing elements easier (e.g. you can use indexOf to find a tile in an island).
addToExistingIsland is a bit trickier: you have to check if either element is part of an existing island, and consolidate those islands if so. If not, the new tile just gets appended to the island.
You don't need to maintain 3 arrays, just one island array that, at the end of ppnBlockValid, will either be length > 1 (invalid), or less (valid).
You cannot just populate the "ppnAddNewIsland" and "ppnAddToExistingIsland" arrays-- these should be functions. If ppnIslands is a class variable, then your addNewIsland function may look something like this:
private function addNewIsland(who) {
ppnIslands.push([who]);
}
... the only optimization I'd probably make is to turn who (passed as an array [col,row]) into a character string, as I mentioned, to make finding whether a tile exists in an island easier.
Then, you need a function like:
private function addToExistingIsland( existing_island_tile, new_tile ) {
}
This function must:
1. check whether these two tiles already exist in the same island (just return if so)
2. check whether new_tile is already on an island (consolidate the two islands into one if so)
3. otherwise, just add new_tile to the island that existing_island_tile is a member of
The code I presented should work pretty much as-is-- although you could add back in the "validNeighbours" logic if you want a quick exit for isolated tiles.

Box2D - FRIM (Frame Rate Independent Movement)

I've been trying to implement a FRIM system for my game for the last couple of days.I did some research and came upon this article - it seemed simple enough to implement so I got started.
Everything seems to work fine except I get some temporal aliasing (moving bodies seem to jump ahead a bit) - this happens when more Box2D steps are processed....I think.
private const FIXED_TIMESTEP:Number = 1 / 60;
private const velocityIterations:int = 8;
private const positionIterations:int = 3;
private var fixedTimestepAccumulator:Number = 0;
private var fixedTimestepAccumulatorRatio:Number = 0;
public function Step(dt:Number):void
{
//dt - time between frames - I'm passing the e.passedTime - from the enter frame event; using Starling
fixedTimestepAccumulator += dt;
var nSteps:uint = Math.floor(fixedTimestepAccumulator / FIXED_TIMESTEP);
if (nSteps > 0)
{
fixedTimestepAccumulator = fixedTimestepAccumulator - nSteps * FIXED_TIMESTEP;
}
fixedTimestepAccumulatorRatio = fixedTimestepAccumulator / FIXED_TIMESTEP;
var nStepsClamped:int = Math.min(nSteps, MAX_STEPS);
for (var i:int = 0; i < nStepsClamped; ++i)
{
resetSmoothStates();
singleStep(FIXED_TIMESTEP);
}
world.ClearForces();
smoothStates();
}
private function resetSmoothStates():void
{
for (var bb:b2Body = world.GetBodyList(); bb; bb = bb.GetNext())
{
if (bb.GetUserData() is MyUserData && bb.GetType() != b2Body.b2_staticBody )
{
//each of my bodies have a reference to their sprite (actor) in userData
var _userdata:MyUserData=bb.GetUserData();
_userdata.x = _userdata.bodyPreviousX = bb.GetPosition().x * RATIO;
_userdata.y= _userdata.bodyPreviousY = - bb.GetPosition().y* RATIO;
_userdata.rotation = _userdata.bodypreviousRotation= _userdata.bodypreviousRotation = - bb.GetAngle();
}
}
}
private function smoothStates():void
{
var oneMinusRatio:Number = 1.0 - fixedTimestepAccumulatorRatio;
for (var bb:b2Body = world.GetBodyList(); bb; bb = bb.GetNext())
{
if (bb.GetUserData() is MyUserData && bb.GetType() != b2Body.b2_staticBody )
{
var userdata=bb.GetUserData();
userdata.x = (fixedTimestepAccumulatorRatio * bb.GetPosition().x * RATIO + oneMinusRatio * userdata.bodyPreviousX) ;
userdata.y = (- fixedTimestepAccumulatorRatio * bb.GetPosition().y * RATIO + oneMinusRatio * userdata.bodyPreviousY) ;
userdata.rotation = (- fixedTimestepAccumulatorRatio * bb.GetAngle() + oneMinusRatio * userdata.bodypreviousRotation);
}
}
}
private function singleStep(dt:Number):void
{
Input();
world.Step(dt, velocityIterations, positionIterations);
}
What am I doing wrong?
Any help, suggestions would be highly appreciated.
Thanks
I did this once in a game I was working on so that I could lockstep the game to a certain update rate (this was in iOS). This was based on the code in Daley's book (Learning iOS Game Programming), which was based on some other article in the web (I believe, it may be Allen Bishop's). The code looked like this:
void GameManager::UpdateGame()
{
const uint32 MAXIMUM_FRAME_RATE = Constants::DEFAULT_OBJECT_CYCLES_PER_SECOND();
const uint32 MINIMUM_FRAME_RATE = 10;
const uint32 MAXIMUM_CYCLES_PER_FRAME = (MAXIMUM_FRAME_RATE/MINIMUM_FRAME_RATE);
const double UPDATE_INTERVAL = (1.0/MAXIMUM_FRAME_RATE);
static double lastFrameTime = 0.0;
static double cyclesLeftOver = 0.0;
double currentTime;
double updateIterations;
currentTime = CACurrentMediaTime();
updateIterations = ((currentTime - lastFrameTime) + cyclesLeftOver);
if(updateIterations > (MAXIMUM_CYCLES_PER_FRAME*UPDATE_INTERVAL))
{
updateIterations = MAXIMUM_CYCLES_PER_FRAME*UPDATE_INTERVAL;
}
while (updateIterations >= UPDATE_INTERVAL)
{
// DebugLogCPP("Frame Running");
updateIterations -= UPDATE_INTERVAL;
// Set the random seed for this cycle.
RanNumGen::SetSeed(_cycleManager->GetObjectCycle());
// Dispatch messages.
_messageManager->SendMessages();
// Update all entities.
_entityManager->Update();
// Update the physics
_gameWorldManager->Update(Constants::DEFAULT_OBJECT_CYCLE_SECONDS());
// Advance the cycle clock.
_cycleManager->Update();
}
cyclesLeftOver = updateIterations;
lastFrameTime = currentTime;
}
I can't put my finger on the specific item that is wrong in yours. However, this part is suspicious:
var nStepsClamped:int = Math.min(nSteps, MAX_STEPS);
Before this, you updated your accumulator with:
fixedTimestepAccumulator = fixedTimestepAccumulator - nSteps * FIXED_TIMESTEP;
But now the actual number of steps you are going to execute may be different because of the clamping (nStepsClamped). So your time accumulation is different than the number of steps you actually execute.
Was this helpful?
I decided to go with another approach. I'm using filtered delta times for the physics (I know this can cause some problems).
Here's what I'm doing now:
//Play around with this filter value if things don't look right
var filter:Number=0.4;
filtered_dt= time_between_frames * filter + filtered_dt * (1 - filter);
//Poll imputs and apply forces
// I use velocityIterations =6, positionIterations=3
world.Step(filtered_dt, velocityIterations, positionIterations);
//move sprites here
world.ClearForces();
Another thing you have to do is to scale the forces you apply to your bodies using filtered_dt so things don't "explode" when the frame rate changes a lot.
Hope this helps some one else ... it's not the perfect solution but it works for me.
If you have slow moving bodies the interpolation method above should work fine too.