-
[RESOLVED] Need Help With Character Movement/Blitting
I'm coding a tile-based platform game and I'm having trouble getting the character movement working. I'm blitting everything in layers (background image, then level tiles, then player and enemies). Here's the document class I'm using:
PHP Code:
package app{
import flash.display.Sprite;
import flash.events.TimerEvent;
import flash.utils.Timer;
import app.loadData.MapData;
import app.keyboard.CheckInput;
import utils.Key;
public class Main extends Sprite {
private var mapData:MapData = new MapData;
private var checkInput:CheckInput = new CheckInput(stage);
private var key:Key = new Key(stage);
private var loaderTimer:Timer = new Timer(25);
private var gameTimer:Timer = new Timer(25);
public static var walkSpeed:Number = new Number(2);
public function Main():void {
addChild(mapData.levelBitmap);
mapData.currentLevel = 2;
mapData.loadMap();
loaderTimer.addEventListener(TimerEvent.TIMER, loadingComplete);
loaderTimer.start();
}
private function loadingComplete(e:TimerEvent):void {
if (mapData.levelLoaded) {
loaderTimer.stop();
loaderTimer.removeEventListener(TimerEvent.TIMER, loadingComplete);
gameTimer.addEventListener(TimerEvent.TIMER, gameLoop);
gameTimer.start();
}
}
private function gameLoop(e:TimerEvent):void {
checkInput.checkKeys();
mapData.drawMap();
}
}
}
And this is the MapData class that blits everything to the screen:
PHP Code:
package app.loadData{
import flash.display.Sprite;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.geom.Rectangle;
import flash.geom.Point;
import flash.events.Event;
import flash.net.URLLoader;
import flash.net.URLRequest;
public class MapData extends Sprite {
private var _currentLevel:int = new int();
private var _levelLoaded:Boolean = false;
private var _bitmapsLoaded:Boolean = false;
private var _charPointX:int;
private var _charPointY:int;
private var levelLoader:URLLoader = new URLLoader();
private var levelXML:XML = new XML();
private var tileHeight:int= 40;
private var tileWidth:int= 40;
private var tileRows:int = 11;
private var tileCols:int = 15;
private var gameHeight:int = 440;
private var gameWidth:int = 600;
private var tileX:int = 0;
private var tileY:int = 0;
private var gr:GroundTileSheet = new GroundTileSheet(200,200);
private var st:StoneTileSheet = new StoneTileSheet(200,200);
private var charTest:CharTest = new CharTest(tileWidth,tileHeight);
private var level1BG:Level1BG = new Level1BG(880,440);
private var level2BG:Level1BG = new Level1BG(880,440);
private var level3BG:Level1BG = new Level1BG(880,440);
private var bgRect:Rectangle = new Rectangle(0,0,gameWidth,gameHeight);
private var bgPoint:Point = new Point(0,0);
private var tileRect:Rectangle = new Rectangle(0,0,tileWidth,tileHeight);
private var tilePoint:Point = new Point(0,0);
private var tileCanvasBD:BitmapData = new BitmapData(gameWidth, gameHeight, true);
private var canvasBD:BitmapData = new BitmapData(gameWidth,gameHeight,false,0x000000);
private var canvasBitmap:Bitmap = new Bitmap(canvasBD);
public function MapData():void {
}
public function loadMap():void {
loadXML();
}
public function drawMap() {
canvasBD.lock();
if (!_bitmapsLoaded) {
loadBitmaps();
}
drawBG();
drawLevel();
drawChar();
canvasBD.unlock();
}
private function loadXML():void {
if (!_levelLoaded) {
levelLoader.addEventListener(Event.COMPLETE, xmlDataLoaded);
levelLoader.load(new URLRequest("app/data/level"+_currentLevel+".xml"));
}
}
private function xmlDataLoaded(e:Event):void {
levelXML = XML(e.currentTarget.data);
_levelLoaded = true;
}
private function loadBitmaps():void {
for (var yCntr:int=0; yCntr < tileRows; yCntr++) {
for (var xCntr:int=0; xCntr < tileCols; xCntr++) {
var levelSplit:String = levelXML.r[yCntr].split(",")[xCntr];
var levelSheet:String = levelSplit.split("-")[0];
var levelCol:int = levelSplit.split("-")[1];
var levelRow:int = levelSplit.split("-")[2];
tileRect.x = int(levelCol) * tileWidth;
tileRect.y = int(levelRow) * tileHeight;
tilePoint.x = xCntr * tileWidth;
tilePoint.y = yCntr * tileHeight;
tileCanvasBD.copyPixels(this[levelSheet], tileRect, tilePoint);
}
}
_charPointX = levelXML.s[0].split(",")[0]*tileWidth;
_charPointY = levelXML.s[0].split(",")[1]*tileHeight;
_bitmapsLoaded = true;
}
private function drawBG():void {
canvasBD.copyPixels(this["level"+_currentLevel+"BG"],bgRect,bgPoint);
}
private function drawLevel():void {
canvasBD.copyPixels(tileCanvasBD, bgRect, bgPoint);
}
private function drawChar():void {
var charPoint:Point = new Point(_charPointX,_charPointY);
var charRect:Rectangle = new Rectangle(0,0,tileWidth,tileHeight);
canvasBD.copyPixels(charTest, charRect, charPoint);
}
public function set currentLevel(level:int):void {
_currentLevel = level;
}
public function get currentLevel():int {
return _currentLevel;
}
public function get levelLoaded():Boolean {
return _levelLoaded;
}
public function get bitmapsLoaded():Boolean {
return _bitmapsLoaded;
}
public function get levelBitmap():Bitmap {
return canvasBitmap;
}
public function set charPointX(pointX:int):void {
_charPointX = pointX;
}
public function get charPointX():int {
return _charPointX;
}
public function set charPointY(pointY:int):void {
_charPointY = pointY;
}
public function get charPointY():int {
return _charPointY;
}
}
}
Then this is my CheckInput class that checks for key presses:
PHP Code:
package app.keyboard{
import flash.events.KeyboardEvent;
import flash.display.DisplayObject;
import flash.display.Stage;
import flash.text.TextField;
import flash.text.TextFormat;
import app.Main;
import app.loadData.MapData;
import utils.Key;
public class CheckInput {
private var key:Key;
private var leftKey:int = 37;
private var upKey:int = 38;
private var rightKey:int = 39;
private var downKey:int = 40;
private var spaceBar:int = 32;
private var keyState:TextField = new TextField();
private var jumpState:TextField = new TextField();
private var keyStateFormat:TextFormat = new TextFormat();
private var jumpStateFormat:TextFormat = new TextFormat();
private var mapD:MapData = new MapData();
public function CheckInput(stageVar:Stage) {
key = new Key(stageVar);
keyStateFormat.align = jumpStateFormat.align = "center";
keyStateFormat.size = jumpStateFormat.size = 14;
keyStateFormat.bold = jumpStateFormat.bold = true;
keyState.width = jumpState.width = 200;
keyState.height = jumpState.height = 30;
keyState.x = jumpState.x = (stageVar.stageWidth/2)-(keyState.width/2);
keyState.y = (stageVar.stageHeight/2)-10;
jumpState.y = (stageVar.stageHeight/2)+10;
keyState.text = "No Keys are Down";
jumpState.text = "Player is Not Jumping";
keyState.setTextFormat(keyStateFormat);
jumpState.setTextFormat(jumpStateFormat);
stageVar.addChild(keyState);
stageVar.addChild(jumpState);
}
public function checkKeys() {
if (key.isDown(leftKey)) {
keyState.text = "Left Key is Down";
jumpState.text = "Player is Not Jumping";
keyState.setTextFormat(keyStateFormat);
jumpState.setTextFormat(jumpStateFormat);
mapD.charPointX -= Main.walkSpeed;
trace(mapD.charPointX);
} else if (key.isDown(upKey)) {
keyState.text = "Up Key is Down";
jumpState.text = "Player is Not Jumping";
keyState.setTextFormat(keyStateFormat);
jumpState.setTextFormat(jumpStateFormat);
mapD.charPointY-=Main.walkSpeed;
trace(mapD.charPointY);
} else if (key.isDown(rightKey)) {
keyState.text = "Right Key is Down";
jumpState.text = "Player is Not Jumping";
keyState.setTextFormat(keyStateFormat);
jumpState.setTextFormat(jumpStateFormat);
mapD.charPointX+=Main.walkSpeed;
trace(mapD.charPointX);
} else if (key.isDown(downKey)) {
keyState.text = "Down Key is Down";
jumpState.text = "Player is Not Jumping";
keyState.setTextFormat(keyStateFormat);
jumpState.setTextFormat(jumpStateFormat);
mapD.charPointY+=Main.walkSpeed;
trace(mapD.charPointY);
} else if (key.isDown(spaceBar)) {
keyState.text = "Space Bar is Down";
jumpState.text = "Player is Jumping";
keyState.setTextFormat(keyStateFormat);
jumpState.setTextFormat(jumpStateFormat);
} else {
keyState.text = "No Keys are Down";
jumpState.text = "Player is Not Jumping";
keyState.setTextFormat(keyStateFormat);
jumpState.setTextFormat(jumpStateFormat);
}
}
}
}
Right now the code is kind of a mess and but you should be able to get an idea how it works. The problem I'm having is that when the _charPointX and _charPointY variables are changed, it doesn't actually move charPoint to reflect the change, so my character doesn't move. The character is placed in the correct tile when the demo is loaded, meaning it's seeing the changes within the MapData class, but when I try to trace _charPointX and _charPointY from the CheckInput class, it gives me 0 and 0 instead of 0 and 6. I'm pretty new to OOP so I'm not sure what I'm doing wrong here - is there some easy solution that I'm just not seeing or am I going to have to completely rewrite everything?
-
Pencil Farmer
You need to be using the same instance of MapData in Main and in CheckInput.
Currently you create a new instance of that class inside each, which means when you change charPointX in the MapData that CheckInput has, it never touches the MapData that Main is checking. Does that make sense?
A quick fix would be to pass the instance you create in Main to CheckInput:
PHP Code:
private var checkInput:CheckInput = new CheckInput(stage, mapData);
Then in the CheckInput constructor just assign that instance of MapData to your mapD variable.
You can probably figure out a better way to deal with that once you start cleaning things up, but that should get it working for now.
Hope that helps.
-
Wow, I don't know how I missed that - I changed it so it passes mapData along to CheckInput and it works perfectly now. One more question - what's the difference between a public variable/function and a public static variable/function? As far as I can tell the only difference is it lets me reference variables in other classes without having to create an instance of the class that variable is in, but most of the time it just seems to break my code.
-
Pencil Farmer
Yeah, you got it...
Static means it's a property of the class itself rather than the object, so you can access it without having to instantiate the class.
Static properties can be useful for holding constants for settings (eg. Hero.MAX_HEALTH).
Static methods are usually things like utility methods (eg. Math.sqrt) that don't depend on instance variables to operate.
If you're having problems it could be that you're trying to access instance variables from within a static method.
Also remember that to refer to a static property you'd use the class name, rather than the instance name.
Someone else here can probably give a more accurate explanation, but that's the jist of it.
Glad you got it working!
-
Ok, so making a variable or function static completely changes the way you need to access it, which is probably why my code is breaking. My biggest problem is that I can't figure out how to access a public static variable from within the same class - I have to make them all private static variables and then use a getter if I want to access them from other classes. So far it really hasn't been a problem, though.
One last question (I hope). I've got an XML file that stores data about my maps - which tiles to place where, what tile sheet they reside on, whether you can walk through them or not, etc. My movement isn't tile-based, though, and I'm having trouble getting it to work.
I added a function to my MapData class called isWalkable. Here's the code for that:
PHP Code:
public function isWalkable(tileX:int,tileY:int):Boolean {
var isWalkableString:String = levelXML.w[tileY].split(",")[tileX];
var isWalkable:Boolean = false;
if (isWalkableString == "true") {
isWalkable = true;
} else {
isWalkable = false;
}
return isWalkable
}
For example, if I passed 0 and 1 to it then it would find whether the tile in row 0 column 1 is walkable, then return true if it is and false if it's not. I'm calling it from the checkKeys function of my CheckInput class like this:
PHP Code:
public function checkKeys():void {
if (key.isDown(leftKey)) {
if (mapD.isWalkable(Math.floor(mapD.charPointX/tileSize), Math.floor(mapD.charPointY/tileSize))) {
mapD.charPointX-=walkSpeed;
}
} if (key.isDown(upKey)) {
if (mapD.isWalkable(Math.floor(mapD.charPointX/tileSize), Math.floor(mapD.charPointY/tileSize))) {
mapD.charPointY-=walkSpeed;
}
} if (key.isDown(rightKey)) {
if (mapD.isWalkable(Math.floor((charX+tileSize)/tileSize), Math.floor(mapD.charPointY/tileSize))) {
mapD.charPointX+=walkSpeed;
}
} if (key.isDown(downKey)) {
if (mapD.isWalkable(Math.floor(mapD.charPointX/tileSize), Math.floor((charY+tileSize)/tileSize))) {
mapD.charPointY+=walkSpeed;
}
}
}
Right now it's working perfectly for the right and bottom edges. When I walk to the left edge of the level, though, I get stuck until I move to the right again, and if I walk to the top edge I can actually move slightly past the top of the level, which gives me this error:
PHP Code:
TypeError: Error #1006: value is not a function.
at app.loadData::MapData/isWalkable()
at app.keyboard::CheckInput/checkKeys()
...
Obviously there's something wrong somewhere, but I can't figure out exactly what it is.
-
Senior Member
You are currently detecting collision with point where character stands now, instead you should do collision check with point where character ends up after moving.
Lets suppose character is at x=100 and there is block at the right side starting from x=101. Detecting collision with current point 100 says you can move right so you move 1 pixel right. Next time you try to move, your character is stuck inside the block because it will always detect collision at 101. So when you are still at 100 and attempt to move right by 1 pixel, you should detect collision with point 101, if there is block you cant move there and you remain at point 100 and you will not end up inside the block. Same way to move left you would detect collision with point 99 before moving the character.
-
Ok, here's the new code:
PHP Code:
public function isWalkable(tileX:int,tileY:int):Boolean { var isWalkableString:String; var isWalkable:Boolean = false; if (tileX >= 0 && tileY >= 0) { isWalkableString = levelXML.w[tileY].split(",")[tileX]; } if (isWalkableString == "true") { isWalkable = true; } return isWalkable }
PHP Code:
public function checkKeys():void { if (key.isDown(leftKey)) { if (mapD.isWalkable(Math.floor((mapD.charPointX-walkSpeed)/tileSize), Math.floor(mapD.charPointY/tileSize))) { mapD.charPointX-=walkSpeed; } } if (key.isDown(upKey)) { if (mapD.isWalkable(Math.floor(mapD.charPointX/tileSize), Math.floor((mapD.charPointY-walkSpeed)/tileSize))) { mapD.charPointY-=walkSpeed; } } if (key.isDown(rightKey)) { if (mapD.isWalkable(Math.floor((charX+tileSize)/tileSize), Math.floor(mapD.charPointY/tileSize))) { mapD.charPointX+=walkSpeed; } } if (key.isDown(downKey)) { if (mapD.isWalkable(Math.floor(mapD.charPointX/tileSize), Math.floor((charY+tileSize)/tileSize))) { mapD.charPointY+=walkSpeed; } } }
I figured out that the error when hitting the top edge was because it was testing for a negative Y value, which was causing it look for an entry in the XML file that doesn't exist. This presents me with a new problem, though. Eventually I'd like to have platforms, and the ability to scroll up, left, down or right. Right now I can't move past the top of the screen, though, since it'll cause charPointY to be negative. So what should I do? I was thinking I might create a container Sprite to hold everything, make it the size of the entire level, and then draw all my bitmaps to that. That way it'll test charPointX and charPointY against the container Sprite instead of the stage. I don't know how that would affect the performance of the game, though, and I'm sure there's a better way of doing it than that. Anyone have any ideas?
EDIT: Never mind the above, I've got the scrolling working. I'm moving the level instead of the character once he gets near any of the edges (I have no idea why that never occurred to me before). Now I just have to add in items and enemies and get to work on some levels.
Last edited by DoomOfTheLiving; 04-08-2009 at 03:31 AM.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|