ActionScript 3 - Walls don't collide properly - actionscript-3

For several weeks I'm been trying to make my topdown game. It went well for some time, but then at some point I wanted to create a scrolling map with walls everywhere. Now, to make it easy to create the map (and add more later) I made a class called "Wall" which I will hit test. This works, when it hits, the map must stop scrolling. It does, so good so far.
Now, when the player moves away from the object, I want the map to be able to scroll again, this works too, but now the player can't move to the side the player came from. I know this is because I need to define the sides, where the player enters, in order tell the game which movement must be set to zero at that point.
You can see the code here:
public function AddWalls(player:MovieClip)
{
WallObjects = new Array();
for (var i:int = 0; i < this.numChildren; i++)
{
var mc = this.getChildAt(i);
if (mc is Wall)
{
var wallobj:Object = new Object();
wallobj.mc = mc;
wallobj.leftside = mc.x;
wallobj.rightside = mc.x + mc.width;
wallobj.topside = mc.y;
wallobj.bottomside = mc.y + mc.height;
wallobj.width = mc.width;
wallobj.height = mc.height;
WallObjects.push(wallobj);
}
}
}
public function EnableCollisionWithWalls():void
{
for (var k:int = 0; k < WallObjects.length; k++)
{
//if (player.y > WallObjects[k].topside && player.y < WallObjects[k].bottomside && player.x > WallObjects[k].leftside && player.x < WallObjects[k].rightside)
if (player.hitTestObject(WallObjects[k].mc))
{
if (player.x > WallObjects[k].leftside && player.x < WallObjects[k].leftside+15)
{
Lefthit = true;
trace(DebugVar);
DebugVar++;
player.x = WallObjects[k].leftside;
Scroll_x = 0;
}
else
if ( player.x < WallObjects[k].leftside -1 || (player.y > WallObjects[k].leftside ))
{
Lefthit = false;
}
if (player.hitTestObject(derp))
{
Lefthit = false;
}
}
}
}
public function EnableMovement():void
{
map.x += Scroll_x;
map.y += Scroll_y;
for (var i:int = 0; i < this.numChildren; i++)
{
var mc = this.getChildAt(i);
if (mc is Wall)
{
mc.x += Scroll_x;
mc.y += Scroll_y;
}
}
}
public function MovementKeysDown(move:KeyboardEvent):void
{
var Speed:int = -5;
switch (move.keyCode)
{
case 37: // venstre knap
Scroll_x = -Speed;
break;
case 38: // op
Scroll_y = -Speed;
break;
case 39: // højre knap
Scroll_x = Speed;
if (Lefthit)
{
Scroll_x = 0;
}
break;
case 40: // ned
Scroll_y = Speed;
break;
default:
}
}
public function MovementKeysUp(move:KeyboardEvent):void
{
switch (move.keyCode)
{
case 37:
Scroll_x = 0;
break;
case 38:
Scroll_y = 0;
break;
case 39:
Scroll_x = 0;
break;
case 40:
Scroll_y = 0;
break;
default:
}
}
Might be some syntax errors (since I removed some code in this editor).
You can see the current version here.
In this version the scroll keeps on going. I did come up with a "fix" for it, by check if the player was 1 pixel away from the movieclip, inside the hit test (which for some reason works, which I guess it shouldn't since it doesn't hit anymore) and then setting the Lefthit to false. However this is not a good solution and if you continue up or down away from the movieclip, you are still not able to go right anymore...
I've been baffled by this for a long time, so I thought it was about time I asked for help. I couldn't find anything on how to control movement in a top-down game, with a scrolling map + wall :/

The simplest (but not most resource friendly) solution (if you anyway have a single storage for walls) is iterating through the walls and instead of using the Flash default hitTest (I don't like the way it works since ActionScript 2) - just check the coordinates and if you see that there's going to be a collision on the next simulation step - handle it according to the game logic.
The most useful optimization for this algorithm is creating a filter/data structure for getting only walls that are near to the player and so can be affected to the test for collisions.

Related

How can I code an object to spawn onto the stage in the Actions panel?

I am trying to introduce a new ball into my pong game, sort of like a power up. I am writing all of my code in the Actions panel in the first frame. The new ball should appear on the stage and start moving around randomly like the original ball. Although I am using a code snippet and not .as file. So all of my code is in the Actions panel(Accessed by pressing f9).
I would also like my dynamic text box to merge with the stage colour so that you can't see the white background.
I can't show you what the fla looks like because I have less than 10 reputation, but the dynamic text box will not merge into the background and instead has a white surrounding. This hides the ball when the ball goes up.
import flash.events.Event;
import flash.ui.Mouse;
//hide mouse
Mouse.hide();
init(); //initialises everything
var bSpeedX:int = -3.5;
var bSpeedY:int = -2.5;
// assign a maximum speed to the AI
var compPaddleSpeed:int = 3.5;
var pScore:int = 0;
var cScore:int = 0;
// Updates the score
function scoreUpdate():void {
playerScore.text = ("Player Score: " + pScore);
computerScore.text = ("AI Score: " + cScore);
}
function init():void //tells flash not to return values
{
stage.addEventListener(Event.ENTER_FRAME, loop);
}
/*we want the ySpeed to be larger if there
is a greater difference between the y
positions of the ball and paddle, so I started with
(gameBallY-padY). To convert this difference
into a number between -1 and 1, I divided
this number by 25, which
is half the height of the paddle. Finally, I wanted
the ySpeed to be more powerful than
just -1 to 1, and after a bit of trial and error
I decided to times by 5 at the end
to change the total magnitude of the new ySpeed.*/
//defying the laws of Physics
function calculategameBallAngle(padY:Number, gameBallY:Number):Number
{
var ySpeed:Number = 5 * ((gameBallY-padY) / 25 );
return ySpeed;
}
//main loop
function loop(e:Event):void
{
//makes the paddle track the mouse
playerPaddle.y = mouseY;
//paddle AI
if(compPaddle.y < gameBall.y - 10){
compPaddle.y += compPaddleSpeed;//make it go up
} else if(compPaddle.y > gameBall.y + 10){
compPaddle.y -= compPaddleSpeed;//make it go down
}
//Collisions
if( playerPaddle.hitTestObject(gameBall) == true ){
if(bSpeedX < 0){
bSpeedX *= -1;
bSpeedY = calculategameBallAngle(playerPaddle.y, gameBall.y);
}
} else if(compPaddle.hitTestObject(gameBall) == true ){
if(bSpeedX > 0){
bSpeedX *= -1;
bSpeedY = calculategameBallAngle(compPaddle.y, gameBall.y);
}
}
//makes the gameBall move
gameBall.x += bSpeedX; //each frame, we add the bSpeedX to the ball's x position.
gameBall.y += bSpeedY; //same for the bSpeedY to the ball's y postion.
// checks to see if the ball misses the paddle
if(gameBall.x <= gameBall.width/2){
gameBall.x = gameBall.width/2;
bSpeedX *= -1;
cScore ++;
scoreUpdate();
//keeps the ball within the stage
} else if(gameBall.x >= stage.stageWidth-gameBall.width/2){
gameBall.x = stage.stageWidth-gameBall.width/2;
bSpeedX *= -1;
pScore ++;
scoreUpdate();
}
if(gameBall.y <= gameBall.height/2){
gameBall.y = gameBall.height/2;
bSpeedY *= -1;
}
else if(gameBall.y >= stage.stageHeight-gameBall.height/2){
gameBall.y = stage.stageHeight-gameBall.height/2;
bSpeedY *= -1;
}
//-------------------------------------------------------
//keeps the player paddle within the stage
//check if paddle is above top of the screen
if(playerPaddle.y - playerPaddle.height/2 < 0){
playerPaddle.y = playerPaddle.height/2;
} else if(playerPaddle.y + playerPaddle.hieght/2 > stage.stageHeight){
playerPaddle.y = stage.stageHeight - playerPaddle.height/2;
//check if paddle is below bottom of the screen
} else if(playerPaddle.y + playerPaddle.height/2 > stage.stageHeight){
playerPaddle.y = stage.stageHeight - playerPaddle.height/2;
}
}
If you only want your ball to be replaced with new one which has diffrent grphics and/or speed you can for example export your ball class to action script:
Library>RMB on your symbol>Properties>ActionScript Linkage>Export for ActionScript
Type your class name under Class: field like "MyBallClass" and hit OK.
Now you can construct this ball in your code and replace old one like this:
var newBall:MyBallClass = new MyBallClass();
addChild(newBall);
newBall.x = gameBall.x; newBall.y = gameBall.y;
gameBall = newBall;
Additionally you can define new variable like var speedModifier:Number = 1; to use with:
gameBall.x += bSpeedX * speedModifier;
gameBall.y += bSpeedY * speedModifier;
And change that also when you change the ball.
If You want to have multiple balls at same time You really should consider build this in OOP. For simplest example in addition to previous one
You can create MyBallClass.as file and write in it something like:
package
{
import flash.display.Sprite;
import flash.geom.Point;
public class MyBallClass extends Sprite
{
public var speedFactor:Number;
public var speed:Point = new Point(-3.5, -2.5);
public function MyBallClass(x:Number, y:Number, speedFactor:Number = 1)
{
this.x = x; this.y = y;
this.speed = speed;
}
}
}
Now you can create container for all the balls in yor game.
var balls:Vector<MyBallClass> = Vector<MyBallClass>([]);
and run your physics for all of them in a loop.
Generally main code would look something like this:
var balls:Vector.<MyBallClass> = Vector.<MyBallClass>([]);
addBall(...)//place first ball.
function loop(e:Event):void {
processBalls();
if(wantToAddNewSuperSpeedBall) addBall(x,y,3);
...
}
function processBalls() {
for (var i:int = 0; i < balls.length; i++) {
detecCollision(balls[i]);
moveBall(balls[i]);
//any code that process a single ball...
}
}
function addBall(x:Number, y:Number, speedFactor:Number = 1) {
var newBall:MyBallClass = new MyBallClass(x,y, speedFactor);
addChild(newBall);
balls.push(newBall);
}
function moveBall(ball:MyBallClass) {
ball.x += ball.speed.x * ball.speedFactor;
ball.y += ball.speed.y * ball.speedFactor;
}
So you should modify all functions which affect ball behavior to work with ball passed as argument, not only one specific instance and then use them for all balls.
There are more to cover in this topic and this isn't maybe the best approach but I've tried to make it easy to understend. There a lot of guides for OOP so you can get better idea about what is going on if you read them.
I hope that helped you somehow.

Compile-time constant error Actionscript 3

I am currently trying to put together a platformer game similar to doodlejump however I am getting two errors that I am unsure of how to fix. Below is the code that I am currently using. My errors are;
**Scene 1, Layer 'Code', Frame 4, Line 181 1046: Type was not found or was not a compile-time constant: stick.
Scene 1, Layer 'Code', Frame 4, Line 207 1180: Call to a possibly undefined method stick.**
var tempStick:stick;
var tmpMc:MovieClip;
stop();
// MONITOR THE ACCELEROMETER
var myAcc:Accelerometer = new Accelerometer();
myAcc.addEventListener(AccelerometerEvent.UPDATE, onAccUpdate);
function onAccUpdate(evt:AccelerometerEvent):void{
accX = evt.accelerationX;
}
//MONITOR THE ENTER_FRAME EVENT
stage.addEventListener(Event.ENTER_FRAME, onMyEnterFrame);
//INIT STAGE WITH CLOUDS
if (firstPass == 1){
liveScore = 0;
accX = 0;
for (var i:int=0; i< 5; i++){
tempStick = new stick;
tempStick.x = Math.random()*stage.stageWidth;
tempStick.y = 0 + i*stage.stageHeight/6;
myVect[i] = tempStick;
addChild(tempStick);
tempStick.cacheAsBitmap = true;
}
firstPass = 2;
}
function onMyEnterFrame(evt:Event):void{
//MOVE X DEPENDING ON THE ACCELEROMETER
MyIcare.x -= (MyIcare.x - (MyIcare.x + accX * 10))*0.6;
//MOVE HEAD TO THE LEFT OR TO THE RIGHT
if(accX > 0) {
MyIcare.gotoAndStop(2);
}else{
MyIcare.gotoAndStop(1);
}
// Vertical speed OF THE ICARE/ANGEL
vVelocity += vAcceleration;
if((MyIcare.y > middleScreen) && (vVelocity < 0)){
// ICARE IS GOING UP
MyIcare.y += vVelocity;
}else{
if(vVelocity > 0){
// ICARE IS GOING DOWN
MyIcare.y += vVelocity;
// TEST IF ICARE TOUCHES A CLOUD
for (var i:int=0; i< 5; i++){
tmpMc = myVect[i];
if (MyIcare.hitTestObject(tmpMc))
{
vVelocity = -20;
}
}
}else{
// THE WORLD IS GOING DOWN
// WHEN ICARE IS IN THE MIDDLE OF THE SCREEN
for (var j:int=0; j< 5; j++){
tmpMc = myVect[j];
tmpMc.y -= vVelocity;
}
liveScore += 5;
}
}
// CHECK IF THE CLOUDS ARE OUT OF THE SCREEN
if(myVect[0] != null){
for (var k:int=0; k< 5; k++){
tmpMc = myVect[k];
if(tmpMc.y > stage.stageHeight){
tmpMc.y = -5;
tmpMc.x = Math.random()*stage.stageWidth;
}
}
}
//ICARE IS OUT OF THE SCREEN
//PAUSE GAME
MyIcare.y = -300;
vVelocity = 0;
vAcceleration = 0;
// PLAY FUNNY SOUND
}
Most likely your problem is you haven't told your library object (stick) to be accessible to code.
To do that, open your library panel in FlashPro, right click your stick asset/movieClip, and choose properties.
On the Symbol Properties Window, check the Export For ActionScript box. Then enter stick as in the Class field.
Now you can instantiate a stick through code.

supplied DisplayObject must be a child of the caller fix

Im confused as to why my codes saying this,
My game creates a procedural generated map and the map is split into chuncks. The map works fine, i can switch areas and stuff no problem but when i delete a tree tile and re-add a grass tile then try to switch areas it tells me "The supplied DisplayObject must be a child of the caller." Iv gottn and fixed and slighty understand this problem, but i feel as if it IS a child of the calleer. idk :c
How my code is set up it creates a world class on my level class, then in that world a worldTiles sprite is created to place the tiles of the world into. This is where the tiles are originally added and deleted
This is where im pretty sure my problem is, the fucntion that deletes a tree tile and adds a grass tile
protected function mouseOnTile()
{
for (var i:int; i < world.tilesInWorld.length; i++)
{
if (mouse.hitTestObject(world.tilesInWorld[i]))
{
trace(world.tilesInWorld[i].Name);
if (world.tilesInWorld[i].Name == "tree")
{
var tx:int = world.tilesInWorld[i].x;
var ty:int = world.tilesInWorld[i].y;
world.worldTiles.removeChild(world.tilesInWorld[i]);
world.treePool.returnSprite(world.tilesInWorld[i]);
world.tilesInWorld.pop();
world.tile = world.tilePool.getSprite();
world.tile.width = world.TILE_SIZE;
world.tile.height = world.TILE_SIZE;
world.tile.x = tx;
world.tile.y = ty;
world.tilesInWorld.push(world.tile);
world.worldTiles.addChild(world.tile);
}
}
}
}
Im sure it has something to do with how im re-adding the grass tile into the worldTiles, but im confused on how else i could do it?
This is the function that deletes the tiles when you walk to a different screen section
public function deleteTiles()
{
if (tilesInWorld.length > 0)
{
for (var i:int = tilesInWorld.length - 1; i >= 0; i--)
{
worldTiles.removeChild(tilesInWorld[i]);
switch (tilesInWorld[i].Name)
{
case "water" :
waterPool.returnSprite(tilesInWorld[i]);
break;
case "shallow" :
shallowPool.returnSprite(tilesInWorld[i]);
break;
case "shell" :
shellPool.returnSprite(tilesInWorld[i]);
break;
case "sand" :
sandPool.returnSprite(tilesInWorld[i]);
break;
case "tree" :
treePool.returnSprite(tilesInWorld[i]);
break;
case "grass" :
tilePool.returnSprite(tilesInWorld[i]);
break;
case "rock" :
rockPool.returnSprite(tilesInWorld[i]);
break;
case "stone" :
stonePool.returnSprite(tilesInWorld[i]);
break;
}
}
tilesInWorld.length = 0;//empty array
generateTile();
}
}
This is where the tiles are generated onto the screen after being deleted
public function generateTile()
{
var Xcalc:int = (X + (800 / TILE_SIZE) / GlobalCode.MAP_SCALE);
var Ycalc:int = (Y + (600 / TILE_SIZE) / GlobalCode.MAP_SCALE);
for (var i:int = X; i < Xcalc; i++)
{
for (var j:int = Y; j < Ycalc; j++)
{
hm = heightmap[i][j];
if ((hm >= 0.84))
{
tile = waterPool.getSprite();
}
else if (((hm >= 0.8) && hm < 0.84))
{
tile = shallowPool.getSprite();
}
else if (((hm >= 0.79) && hm < 0.799))
{
tile = shellPool.getSprite();
}
else if (((hm >= 0.7) && hm < 0.8))
{
tile = sandPool.getSprite();
}
else if (((hm >= 0.35) && hm < 0.4))
{
tile = treePool.getSprite();
}
else if (((hm >= 0.2) && hm < 0.7))
{
tile = tilePool.getSprite();
}
else if (((hm >= 0.09) && hm < 0.2))
{
tile = stonePool.getSprite();
}
else
{
tile = rockPool.getSprite();
}
tile.width = TILE_SIZE;
tile.height = TILE_SIZE;
worldTiles.x = 0;
worldTiles.y = 0;
tile.x = TILE_SIZE * (i % 800);
tile.y = TILE_SIZE * (j % 600);
tilesInWorld.push(tile);
worldTiles.addChild(tile);
}
}
}
I believe your problem is here:
for (var i:int; i < world.tilesInWorld.length; i++)
{
...
world.worldTiles.removeChild(world.tilesInWorld[i]);
world.treePool.returnSprite(world.tilesInWorld[i]);
world.tilesInWorld.pop();
...
}
You are lopping forward through the array, using removeChild on an element in the array, and using "pop" which removes the last item of the array, not the item that was actually removed. You eventually will hit an item that was already removed. Additionally, your i pointer changes each time you pop, which means the loop will never hit every item and is fundamentally flawed.
The DisplayObject used as the argument in dO.removeChild() must be defined, non-null, and a child of dO (i.e. added with dO.addChild(). If it does not meet all of those requirements, it will error out.
To fix this, use splice() instead of pop() (which will allow you to remove a specific element in an array) and go backwards through the array (which will handle the i pointer issues)
for (var i:int = world.tilesInWorld.length - 1; i >= 0; --i)
{
...
world.worldTiles.removeChild(world.tilesInWorld[i]);
world.treePool.returnSprite(world.tilesInWorld[i]);
world.tilesInWorld.splice(i, 1);
...
}
You can also loop forward through the array, but you need to modify the pointer. This is slower and more error prone than going backwards, but can work just as well (when I say slower, we're talking microseconds difference unless you are doing massive computations).
for (var i:int; i < world.tilesInWorld.length; i++)
{
...
world.worldTiles.removeChild(world.tilesInWorld[i]);
world.treePool.returnSprite(world.tilesInWorld[i]);
world.tilesInWorld.splice(i, 1);
--i;
...
}
Additionally, and this is just a syntax/readability thing, you should never rely on a datatype's default value. int will default to 0, but you should still declare it as var i:int = 0 to make it easy to change in the future, standardized and easy to read, and so that you could easily change it to a Number, which has a much, much higher max value than int but defaults to NaN.
This does not make any sense. Since you are removing all element from the array you can jusr forget about pop or slice and do
world.tilesInWorld.length = 0;
after the loop.
Since you are removing all object from world.worldTiles don't bother removing them one by one and do:
world.worldTiles.removeChildren()
after the loop.
Finally you are left with a simple loop where you only do:
world.treePool.returnSprite(world.tilesInWorld[i]);
It's a case (in your case and in the answer given) where you both try very hard to make a simple code as complicated as possible.

Constant Character Movement - AS3

How am I (am I?) suposed to make character movement with Box2D? I'll need the player to collide with the floor and others, however when it comes to moving him right or left, impulses aren't very handy. At least not what I have right now.
private function update(e:Event):void {
if (keys[Keyboard.RIGHT]) {
impulse_x = 3;
impulse_y = 0;
mainChar.ApplyImpulse(new b2Vec2(impulse_x, impulse_y), mainChar.GetPosition());
} else {
impulse_x = 0;
impulse_y = 0;
mainChar.ApplyImpulse(new b2Vec2(impulse_x, impulse_y), mainChar.GetPosition());
}
}
How do I move my character in a steady pace, as opposed to what the code above does which is accelerate mainChar every update when the right key is being pressed.
EDIT:
So there was this code there but it's on c++ I think - I'd appreciate a translation, since it's so small, to AS3 ^^ as I have no idea what float is doing, syntax is a bit confusing to me.
b2Vec2 vel = body->GetLinearVelocity();
float desiredVel = 0;
switch ( moveState )
{
case MS_LEFT: desiredVel = -5; break;
case MS_STOP: desiredVel = 0; break;
case MS_RIGHT: desiredVel = 5; break;
}
float velChange = desiredVel - vel.x;
float impulse = body->GetMass() * velChange; //disregard time factor
body->ApplyLinearImpulse( b2Vec2(impulse,0), body->GetWorldCenter() );
In AS3
switch (moveState)
{
case "RIGHT": desiredVel = 1.4; break;
case "LEFT": desiredVel = -1.4; break;
case "STOP": desiredVel = 0; break;
}
velChange = desiredVel - mainChar.GetLinearVelocity().x;
impulse.x = velChange;
impulse.y = 0.0;
mainChar.ApplyImpulse(impulse, mainChar.GetWorldCenter());
(Should I delete the initial part of the post and leave the answer?)
You should use a force. It's well described here: http://www.iforce2d.net/b2dtut/forces

How to call AS3 Switch

I am struggling a bit with my switch statement (I've never used switch before). I have a hit test for when my object reaches the top or bottom of the stage. When this happens, I want to switch states (the game in question is a simple platformer that allows the player to switch the gravity when they hit a new surface. Below is my current code:
{
...
if(player.hitTestObject(bottom)) {
//Switch state to normal
}
if(player.hitTestObject(top)) {
//Switch state to inverted
}
}
switch (myGrav){
case "NORMAL":
trace("Normal")
player.gotoAndPlay(1);
oktoJump = false;
player.y = 376.5;
case "INVERTED":
trace("Inverted")
player.gotoAndPlay(8);
oktoJump = false;
player.y = 12;
}
Thanks!
Cases within your switch statement are missing a break; therefore, code will continue to execute through the switch statement.
This should be:
var myGrav:String = "NORMAL";
if (player.hitTestObject(bottom))
myGrav = "NORMAL";
if (player.hitTestObject(top))
myGrav = "INVERTED";
switch (myGrav)
{
case "NORMAL":
trace("Normal")
player.gotoAndPlay(1);
oktoJump = false;
player.y = 376.5;
break;
case "INVERTED":
trace("Inverted")
player.gotoAndPlay(8);
oktoJump = false;
player.y = 12;
break;
}
I prefer less variables when I can get away with it.
switch (true){
case (player.hitTestObject(bottom)):
trace("Normal")
player.gotoAndPlay(1);
oktoJump = false;
player.y = 376.5;
break;
case (player.hitTestObject(top)):
trace("Inverted")
player.gotoAndPlay(8);
oktoJump = false;
player.y = 12;
break;
}