Hello! I am pleased to bring what I think is possibly the best MScale ever made. It's relatively simple to implement, works perfectly for both software and hardware rendering, any game size AND there's no need for any UI scaling as it just works So let's get to it!
Starting off in WebMain.as add these three variables.
Code:
public static var StageWidth:int;
public static var StageHeight:int;
private var resized_:Boolean;
Then in private function setup() after STAGE.addEventListener(Event.ENTER_FRAME, onEnterFrame);
Code:
STAGE.addEventListener(Event.RESIZE, updateStageSize);
setStageSize();
Don't worry about the red, that will be fixed eventually. Replace private static function onEnterFrame(event:Event) with this
Code:
private function onEnterFrame(event:Event) : void
{
SoundEffectLibrary.clear();
if (!resized_){
setStageSize();
}
resized_ = false;
}
private function updateStageSize(event:Event) : void
{
setStageSize();
resized_ = true;
}
private function setStageSize() : void
{
StageWidth = stage.stageWidth / Parameters.data_.mScale;
StageHeight = stage.stageHeight / Parameters.data_.mScale;
}
After, go to Parameters.as and inside public static function setDefaults() add
Code:
setDefault("mScale", 1);
Now in Camera.as replace public function configureCamera(object:GameObject, isHallucinating:Boolean) with
Code:
public function configureCamera(object:GameObject, isHallucinating:Boolean) : void
{
var screenRect:Rectangle = correctCameraView();
var cameraAngle:Number = Parameters.data_.cameraAngle;
this.configure(object.x_,object.y_,12,cameraAngle,screenRect,false);
this.isHallucinating_ = isHallucinating;
}
private function correctCameraView() : Rectangle
{
var width:int = WebMain.StageWidth - 200 * (WebMain.StageWidth / 800);
var height:int = WebMain.StageHeight;
var y:int = Boolean(Parameters.data_.centerOnPlayer)?height*(CENTER_SCREEN_RECT.y/600):height*(OFFSET_SCREEN_RECT.y/600);
var x:int = width / 2;
return new Rectangle(-x, y, width, height);
}
Now for the software MAGIC!! In Map.as replace the entire public function draw(camera:Camera, time:int) with
Code:
public function draw(camera:Camera, time:int) : void
{
var isGpuRender:Boolean = Parameters.isGpuRender(); // cache result for faster access
Parameters.GPURenderFrame = isGpuRender;
if (wasLastFrameGpu != isGpuRender) {
var context:Context3D = WebMain.STAGE.stage3Ds[0].context3D;
if (wasLastFrameGpu && context != null &&
context.driverInfo.toLowerCase().indexOf("disposed") == -1) {
context.clear();
context.present();
}
else {
map_.graphics.clear();
}
signalRenderSwitch.dispatch(wasLastFrameGpu);
wasLastFrameGpu = isGpuRender;
}
var filter:uint = 0;
var render3D:Render3D = null;
var i:int = 0;
var square:Square = null;
var go:GameObject = null;
var bo:BasicObject = null;
var yi:int = 0;
var dX:Number = NaN;
var dY:Number = NaN;
var distSq:Number = NaN;
var b:Number = NaN;
var t:Number = NaN;
var d:Number = NaN;
var screenRect:Rectangle = camera.clipRect_;
x = 300;
y = Boolean(Parameters.data_.centerOnPlayer)?-Camera.CENTER_SCREEN_RECT.y:-Camera.OFFSET_SCREEN_RECT.y;
stage.scaleMode = StageScaleMode.NO_SCALE;
scaleX = 800 / WebMain.StageWidth;
scaleY = 600 / WebMain.StageHeight;
var distW:Number = (-screenRect.y - screenRect.height / 2) / 50;
var screenCenterW:Point = new Point(camera.x_ + distW * Math.cos(camera.angleRad_ - Math.PI / 2),camera.y_ + distW * Math.sin(camera.angleRad_ - Math.PI / 2));
if(this.background_ != null)
{
this.background_.draw(camera,time);
}
this.visible_.length = 0;
this.visibleUnder_.length = 0;
this.visibleSquares_.length = 0;
this.topSquares_.length = 0;
var delta:int = camera.maxDist_;
var xStart:int = Math.max(0,screenCenterW.x - delta);
var xEnd:int = Math.min(this.width_ - 1,screenCenterW.x + delta);
var yStart:int = Math.max(0,screenCenterW.y - delta);
var yEnd:int = Math.min(this.height_ - 1,screenCenterW.y + delta);
this.graphicsData_.length = 0;
this.graphicsDataStageSoftware_.length = 0;
this.graphicsData3d_.length = 0;
// visible tiles
for(var xi:int = xStart; xi <= xEnd; xi++)
{
for(yi = yStart; yi <= yEnd; yi++)
{
square = this.squares_[xi + yi * this.width_];
if(square != null)
{
dX = screenCenterW.x - square.center_.x;
dY = screenCenterW.y - square.center_.y;
distSq = dX * dX + dY * dY;
if(distSq <= camera.maxDistSq_)
{
square.lastVisible_ = time;
square.draw(this.graphicsData_,camera,time);
this.visibleSquares_.push(square);
if(square.topFace_ != null)
{
this.topSquares_.push(square);
}
}
}
}
}
// visible game objects
for each(go in this.goDict_)
{
go.drawn_ = false;
square = go.square_;
if(!(square == null || square.lastVisible_ != time))
{
go.drawn_ = true;
go.computeSortVal(camera);
if(go.props_.drawUnder_)
{
if(go.props_.drawOnGround_)
{
go.draw(this.graphicsData_,camera,time);
}
else
{
this.visibleUnder_.push(go);
}
}
else
{
this.visible_.push(go);
}
}
}
// visible basic objects (projectiles, particles and such)
for each(bo in this.boDict_)
{
bo.drawn_ = false;
square = bo.square_;
if(!(square == null || square.lastVisible_ != time))
{
bo.drawn_ = true;
bo.computeSortVal(camera);
this.visible_.push(bo);
}
}
// draw visible under
if(this.visibleUnder_.length > 0)
{
this.visibleUnder_.sortOn(VISIBLE_SORT_FIELDS,VISIBLE_SORT_PARAMS);
for each(bo in this.visibleUnder_)
{
bo.draw(this.graphicsData_,camera,time);
}
}
// draw shadows
this.visible_.sortOn(VISIBLE_SORT_FIELDS,VISIBLE_SORT_PARAMS);
if(Parameters.data_.drawShadows)
{
for each(bo in this.visible_)
{
if(bo.hasShadow_)
{
bo.drawShadow(this.graphicsData_,camera,time);
}
}
}
// draw visible
for each(bo in this.visible_)
{
bo.draw(this.graphicsData_,camera,time);
if (isGpuRender) {
bo.draw3d(this.graphicsData3d_);
}
}
// draw top squares
if(this.topSquares_.length > 0)
{
for each(square in this.topSquares_)
{
square.drawTop(this.graphicsData_,camera,time);
}
}
// draw breath overlay
if(this.player_ != null && this.player_.breath_ >= 0 && this.player_.breath_ < Parameters.BREATH_THRESH)
{
b = (Parameters.BREATH_THRESH - this.player_.breath_) / Parameters.BREATH_THRESH;
t = Math.abs(Math.sin(time / 300)) * 0.75;
BREATH_CT.alphaMultiplier = b * t;
this.hurtOverlay_.transform.colorTransform = BREATH_CT;
this.hurtOverlay_.visible = true;
this.hurtOverlay_.x = screenRect.left;
this.hurtOverlay_.y = screenRect.top;
}
else
{
this.hurtOverlay_.visible = false;
}
// draw side bar gradient
if(this.player_ != null)
{
this.gradientOverlay_.visible = true;
this.gradientOverlay_.x = screenRect.right - 10;
this.gradientOverlay_.y = screenRect.top;
this.gradientOverlay_.height = 600 * (600 / scaleY);
}
else
{
this.gradientOverlay_.visible = false;
}
// draw hw capable screen filters
if(isGpuRender && Renderer.inGame)
{
filter = this.getFilterIndex();
render3D = StaticInjectorContext.getInjector().getInstance(Render3D);
render3D.dispatch(this.graphicsData_,this.graphicsData3d_,width_,height_,camera,filter);
for(i = 0; i < this.graphicsData_.length; i++)
{
if(this.graphicsData_[i] is GraphicsBitmapFill && GraphicsFillExtra.isSoftwareDraw(GraphicsBitmapFill(this.graphicsData_[i])))
{
this.graphicsDataStageSoftware_.push(this.graphicsData_[i]);
this.graphicsDataStageSoftware_.push(this.graphicsData_[i + 1]);
this.graphicsDataStageSoftware_.push(this.graphicsData_[i + 2]);
}
else if(this.graphicsData_[i] is GraphicsSolidFill && GraphicsFillExtra.isSoftwareDrawSolid(GraphicsSolidFill(this.graphicsData_[i])))
{
this.graphicsDataStageSoftware_.push(this.graphicsData_[i]);
this.graphicsDataStageSoftware_.push(this.graphicsData_[i + 1]);
this.graphicsDataStageSoftware_.push(this.graphicsData_[i + 2]);
}
}
if(this.graphicsDataStageSoftware_.length > 0)
{
map_.graphics.clear();
map_.graphics.drawGraphicsData(this.graphicsDataStageSoftware_);
if(this.lastSoftwareClear)
{
this.lastSoftwareClear = false;
}
}
else if(!this.lastSoftwareClear)
{
map_.graphics.clear();
this.lastSoftwareClear = true;
}
if(time % 149 == 0)
{
GraphicsFillExtra.manageSize();
}
}
else
{
map_.graphics.clear();
map_.graphics.drawGraphicsData(this.graphicsData_);
}
// draw filters
this.map_.filters.length = 0;
if(this.player_ != null && (this.player_.condition_ & ConditionEffect.MAP_FILTER_BITMASK) != 0)
{
var filters:Array = [];
if(this.player_.isDrunk())
{
d = 20 + 10 * Math.sin(time / 1000);
filters.push(new BlurFilter(d,d));
}
if(this.player_.isBlind())
{
filters.push(BLIND_FILTER);
}
this.map_.filters = filters;
}
else if(this.map_.filters.length > 0)
{
this.map_.filters = [];
}
this.mapOverlay_.draw(camera,time);
this.partyOverlay_.draw(camera,time);
stage.scaleMode = StageScaleMode.EXACT_FIT;
}
Now if you play, you should see the game scale with your screen size. However, you'll notice it doesn't work with hardware accel. So let's do the hardware MAGIC, go to Renderer.as and replace private function renderScene with
Code:
private function renderScene(graphicsDatas:Vector.<IGraphicsData>, grahpicsData3d:Vector.<Object3DStage3D>, mapWidth:Number, mapHeight:Number, camera:Camera) : void
{
var test:int = 0;
var graphicsData:IGraphicsData = null;
this.context3D.clear();
var finalTransform:Matrix3D = new Matrix3D();
var index3d:uint = 0;
this.widthOffset_ = -mapWidth / 2;
this.heightOffset_ = mapHeight / 2;
this.UpdateCameraMatrix(camera);
for each(graphicsData in graphicsDatas)
{
this.context3D.GetContext3D().setCulling(Context3DTriangleFace.NONE);
if(graphicsData is GraphicsBitmapFill && !GraphicsFillExtra.isSoftwareDraw(GraphicsBitmapFill(graphicsData)))
{
try
{
test = GraphicsBitmapFill(graphicsData).bitmapData.width;
}
catch(e:Error)
{
trace("ERROR CAUGHT -- Invalid Bitmap Data");
continue;
}
this.graphic3D_.setGraphic(GraphicsBitmapFill(graphicsData),this.context3D);
finalTransform.identity();
finalTransform.append(this.graphic3D_.getMatrix3D());
finalTransform.appendScale(1 / Stage3DConfig.HALF_WIDTH,1 / Stage3DConfig.HALF_HEIGHT,1);
finalTransform.appendScale(800 / WebMain.StageWidth, 600 / WebMain.StageHeight, 1);
finalTransform.appendTranslation(this.tX / Stage3DConfig.WIDTH,this.tY / Stage3DConfig.HEIGHT,0);
this.context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,0,finalTransform,true);
this.graphic3D_.render(this.context3D);
}
if(graphicsData is GraphicsGradientFill)
{
this.context3D.GetContext3D().setProgram(this.shadowProgram_);
this.graphic3D_.setGradientFill(GraphicsGradientFill(graphicsData),this.context3D,Stage3DConfig.HALF_WIDTH,Stage3DConfig.HALF_HEIGHT);
finalTransform.identity();
finalTransform.append(this.graphic3D_.getMatrix3D());
finalTransform.appendScale(800 / WebMain.StageWidth, 600 / WebMain.StageHeight, 1);
finalTransform.appendTranslation(this.tX / Stage3DConfig.WIDTH,this.tY / Stage3DConfig.HEIGHT,0);
this.context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,0,finalTransform,true);
this.context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT,4,Vector.<Number>([0.5,0.25,0,0]));
this.graphic3D_.renderShadow(this.context3D);
}
if(graphicsData == null && grahpicsData3d.length != 0)
{
try
{
this.context3D.GetContext3D().setProgram(this.program2);
this.context3D.GetContext3D().setCulling(Context3DTriangleFace.BACK);
grahpicsData3d[index3d].UpdateModelMatrix(this.widthOffset_,this.heightOffset_);
finalTransform.identity();
finalTransform.append(grahpicsData3d[index3d].GetModelMatrix());
finalTransform.append(this.cameraMatrix_);
finalTransform.append(this._projection);
finalTransform.appendScale(800 / WebMain.StageWidth, 600 / WebMain.StageHeight, 1);
finalTransform.appendTranslation(this.tX / Stage3DConfig.WIDTH,this.tY / Stage3DConfig.HEIGHT * 11.5,0);
this.context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,0,finalTransform,true);
this.context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,8,grahpicsData3d[index3d].GetModelMatrix(),true);
grahpicsData3d[index3d].draw(this.context3D.GetContext3D());
index3d++;
}
catch(e:Error)
{
trace("ERROR CAUGHT -- Invalid Bitmap Data");
continue;
}
}
}
}
Whew that was a lot, almost there! Now you should see the same effect for both rendering modes. Nice! Now it's time for the final bit, actively changing the mscale. Go to MapUserInput.as and replace private function onMouseWheel(event:MouseEvent) with
Code:
private function onMouseWheel(event:MouseEvent) : void
{
if (event.ctrlKey)
{
if(event.delta > 0)
{
Parameters.data_.mScale = Math.min(Parameters.data_.mScale + 0.05, 2);
}
else
{
Parameters.data_.mScale = Math.max(Parameters.data_.mScale - 0.05, 0.5);
}
return;
}
if(event.delta > 0)
{
this.miniMapZoom.dispatch(MiniMapZoomSignal.IN);
}
else
{
this.miniMapZoom.dispatch(MiniMapZoomSignal.OUT);
}
}
And that it! The perfect mscale. All UI is based on the 800x600 window so no need for extra scaling, and all filter effects work to.