 Parallax Sprites. This Time with Bitmaps |
Posted - 6/28/2007 11:30:13 PM | 
Click the image to check this one out. I spent an hour or so modifying my code to place bitmaps in the experiment, and another hour figuring out how to make simple pixel-ish images in The Gimp. The problem with The Gimp is that I automatically expect it will be less user-friendly than it is, just based on reputation. It is actually quite intuitive, from a developer/nerd perspective.
Anyway, I have made the engine a little more robust. Now instead of drawing each ParallaxSprite to the display container one at a time, I cycle through the ParallaxSprites and attach the bottom-most layer from each, then cycle through again and add the next layer, and again and so on until all layers of each sprite have been drawn. This allows for "meshing" of objects which overlap, instead of one being drawn completely on top of another. Since the position of each of the display objects is controlled by its respective class, I can still move everything around without any performance loss over rendering each ParallaxSprite individually.
I suppose the next step is having the ground scroll along with everything else. I would like to have this work as a real movement through space, and not simply wrapping the tiles/sprites, as is currently happening with the trees.
Anywho, here are the updated classes:
World.as
package {
import flash.display.*;
import flash.events.*
[SWF(backgroundColor="0x000000",width="640",height="640",frameRate="50")]
public class World extends Sprite {
private var sprites:Array = [];
private var totalSprites:Number = 30;
private var maxHeight:Number = 0;
public var centerX:Number;
public var centerY:Number;
private var dX:Number;
private var dY:Number;
private var dOff:Number;
private var velocity:Number;
private var theta:Number;
[Embed("ground.jpg")]
private var Ground:Class;
public function World() {
addEventListener(Event.ENTER_FRAME,init);
}
private function init(e:Event):void {
if(!stage) return;
removeEventListener(Event.ENTER_FRAME,init);
centerX = stage.stageWidth/2;
centerY = stage.stageHeight/2;
var g:Bitmap = new Ground();
var b:BitmapData = new BitmapData(g.width,g.height);
b.draw(g);
graphics.beginBitmapFill(b);
graphics.drawRect(0,0,stage.stageWidth,stage.stageHeight);
graphics.endFill();
graphics.beginFill(0xff0000,100);
graphics.drawCircle(centerX,centerY,2.5);
graphics.endFill();
for(var i:Number = 0;i<totalSprites;i++) {
sprites[i] = new ParallaxSprite(
this,
Math.round(Math.random()*stage.stageWidth/100)*100,
Math.round(Math.random()*stage.stageHeight/100)*100
);
sprites[i].cacheAsBitmap = true;
maxHeight = Math.max(sprites[i].layers,maxHeight);
}
for(i = 0;i<maxHeight;i++) {
for(var j:Number = 0;j<totalSprites;j++) {
if(sprites[j].spriteLayers[i]) {
addChild(sprites[j].spriteLayers[i]);
sprites[j].spriteLayers[i].cacheAsBitmap = true;
}
}
}
addEventListener(Event.ENTER_FRAME,moveSprites);
}
private function moveSprites(e:Event):void {
dX = centerX - mouseX;
dY = centerY - mouseY;
theta = Math.atan2(dY,dX);
dOff = Math.sqrt(dX*dX+dY*dY);
velocity = dOff/50;
for(var i:Number = 0;i<totalSprites;i++) {
sprites[i].x += Math.cos(theta)*velocity;
sprites[i].y += Math.sin(theta)*velocity;
if(sprites[i].x < -30) sprites[i].x = stage.stageWidth + 30;
else if(sprites[i].x > stage.stageWidth+30) sprites[i].x = -30;
if(sprites[i].y < -30) sprites[i].y = stage.stageHeight + 30;
else if(sprites[i].y > stage.stageHeight+30) sprites[i].y = -30;
sprites[i].detectCenter();
}
}
}
}
ParallaxSprite.as
package {
import flash.display.*
public class ParallaxSprite extends Sprite {
public var layers:Number;
private var offsetRatio:Number = 1.05;
private var sizeOffset:Number = 1;
private var maxCenterDistance:Number;
private var world:World;
private var dX:Number = 0;
private var dY:Number = 0;
private var d:Number = 0;
private var r:Number = 0;
public var spriteLayers:Array = new Array();
[Embed("tree_layer_0.png")]
private var Tree_0:Class;
[Embed("tree_layer_1.png")]
private var Tree_1:Class;
[Embed("tree_layer_2.png")]
private var Tree_2:Class;
[Embed("tree_layer_3.png")]
private var Tree_3:Class;
public function ParallaxSprite($world:World,$x:Number,$y:Number) {
world = $world;
x = $x;
y = $y;
maxCenterDistance = Math.min(world.centerX,world.centerY)/2
init();
}
private function init():void {
layers = 23;
var rW:Number = Math.round(Math.random()*25)+25;
var rH:Number = Math.round(Math.random()*30)+20;
var rC:Number = Math.round(Math.random()*10)*0x111111;
for(var i:Number=0;i<20;i++) {
spriteLayers[i] = new Tree_0();
}
spriteLayers[20] = new Tree_1();
spriteLayers[21] = new Tree_2();
spriteLayers[22] = new Tree_3();
for(i=0;i<22;i++) {
spriteLayers[i].x = x-50;
spriteLayers[i].y = x-50;
spriteLayers[i].cacheAsBitmap=true;
}
}
public function detectCenter():void {
dX = x - world.centerX;
dY = y - world.centerY;
d = Math.sqrt(dX*dX+dY*dY);
var o:Number = d/maxCenterDistance*offsetRatio;
r = Math.atan2(dY,dX);
for(var i:Number = 0;i<spriteLayers.length;i++) {
spriteLayers[i].x = x+i*o*Math.cos(r)-50;
spriteLayers[i].y = y+i*o*Math.sin(r)-50;
}
}
}
}
| |
 Parallax Sprite |
Posted - 6/27/2007 11:10:53 PM | I just posted another experiment. This one is the beginnings of a parallax sprite engine, inspired by the Dixie Chicks Underground website created by WeFail. Seems easy enough so far, although I have done nothing but throw up some layered graphics.
You will need the Flash 9 plugin to see this one.
Here is the rough draft of the ParallaxSprite class:
package {
import flash.display.*
public class ParallaxSprite extends Sprite {
private var layers:Number;
private var offsetRatio:Number = 1.01;
private var sizeOffset:Number = 1.1;
private var maxCenterDistance:Number;
private var world:World;
private var dX:Number = 0;
private var dY:Number = 0;
private var d:Number = 0;
private var r:Number = 0;
private var spriteLayers:Array = new Array();
public function ParallaxSprite($world:World,$x:Number,$y:Number) {
world = $world;
x = $x;
y = $y;
maxCenterDistance = Math.min(world.centerX,world.centerY)/2
init();
}
private function init():void {
layers = Math.round(Math.random()*30)+30;
var rW:Number = Math.round(Math.random()*25)+25;
var rH:Number = Math.round(Math.random()*30)+20;
var rC:Number = Math.round(Math.random()*10)*0x111111;
for(var i:Number = 0;i<layers;i++) {
spriteLayers[i] = new Shape();
if(i%10==0 || i==layers-1) spriteLayers[i].graphics.lineStyle(1,0xededed,100);
spriteLayers[i].graphics.beginFill(rC,100);
spriteLayers[i].graphics.drawRect(-rW/2 * sizeOffset,-rH/2 * sizeOffset,rW * sizeOffset,rH * sizeOffset);
spriteLayers[i].graphics.endFill();
spriteLayers[i].x = i;
spriteLayers[i].y = i;
spriteLayers[i].cacheAsBitmap=true;
addChild(spriteLayers[i]);
sizeOffset *= offsetRatio;
}
}
public function detectCenter():void {
dX = x - world.centerX;
dY = y - world.centerY;
d = Math.sqrt(dX*dX+dY*dY);
var o:Number = d/maxCenterDistance*offsetRatio;
r = Math.atan2(dY,dX);
for(var i:Number = 0;i<spriteLayers.length;i++) {
spriteLayers[i].x = i*o*Math.cos(r);
spriteLayers[i].y = i*o*Math.sin(r);
}
}
}
}
And here is the World class which creates and powers the ParallaxSprites:
package {
import flash.display.Sprite;
import flash.events.*
[SWF(backgroundColor="0x000000",width="640",height="640",frameRate="50")]
public class World extends Sprite {
private var sprites:Array = [];
private var totalSprites:Number = 20;
public var centerX:Number;
public var centerY:Number;
public function World() {
addEventListener(Event.ENTER_FRAME,init);
}
private function init(e:Event):void {
if(!stage) return;
removeEventListener(Event.ENTER_FRAME,init);
centerX = stage.stageWidth/2;
centerY = stage.stageHeight/2;
graphics.beginFill(0xff0000,100);
graphics.drawCircle(centerX,centerY,5);
graphics.endFill();
for(var i:Number = 0;i<totalSprites;i++) {
sprites[i] = new ParallaxSprite(
this,
Math.round(Math.random()*stage.stageWidth/50)*50,
Math.round(Math.random()*stage.stageHeight/20)*20
);
sprites[i].cacheAsBitmap = true;
addChild(sprites[i]);
}
addEventListener(Event.ENTER_FRAME,moveSprites);
}
private function moveSprites(e:Event):void {
for(var i:Number = 0;i<totalSprites;i++) {
sprites[i].x--;
if(sprites[i].x < -30) {
sprites[i].x = stage.stageWidth + 30;
sprites[i].y = Math.round(Math.random()*stage.stageHeight-40)+20;
}
sprites[i].detectCenter();
}
}
}
}
| |
 Where Does He Find the Time? |
Posted - 6/26/2007 7:42:11 AM | So I did a Google search for "game mechanics" - a phrase which totally escaped me in the past - and on the first page I discovered a site called 300, wherein the author is attempting to come up with a new game mechanic every day for 30 days. So far he is up to 48. He has included screenshots of all of the games, and a lot of them - particularly "Pacman as an RPG" look pretty cool!
I imagine he is probably a user here a gd.net.
| |
 Playing with the Blur Filter |
Posted - 6/23/2007 1:16:49 AM | I just finished playing around with the blur filter, combined with double buffering, in Actionscript 3. You can see the result here.
The class I am using is as follows:
package{
import flash.display.*;
import flash.events.*;
import flash.ui.Keyboard;
import flash.geom.*;
import flash.filters.BlurFilter;
[SWF(backgroundColor="0x000000",width="540",height="540",frameRate="50")]
public class KeyCodes extends Sprite {
[Embed(source="ship_1.gif")]
private var Ship:Class;
private var ship:Bitmap;
private var ball:Sprite;
private var flame:Sprite;
private var _bitmap:BitmapData;
private var _buffer:BitmapData;
private var _buffer_clear:BitmapData;
private var _blur_layer:BitmapData;
private var _image:Bitmap;
private var _blurFilter:BlurFilter;
private var SPEED:Number = Math.PI*2/100;
private var THETA:Number = Math.PI*.5;
private var RAD_DEG:Number = 180/Math.PI;
private var radius:Number;
private var flameRadius:Number;
private var centerX:Number;
private var centerY:Number;
private var angle:Number;
private var xDirection:Number = 0;
public function KeyCodes() {
_blurFilter = new BlurFilter();
addEventListener(Event.ENTER_FRAME,init);
}
private function init(e:Event):void {
if(!stage) return;
removeEventListener(Event.ENTER_FRAME,init);
ball = new Sprite();
ship = new Ship();
ship.x = -(ship.width/2);
ship.y = -(ship.height/2);
ship.rotation = 90;
ball.addChild(ship);
flame = new Sprite();
flame.graphics.beginFill(0x990000,100);
flame.graphics.drawCircle(4,-10,4);
flame.graphics.endFill();
flame.graphics.beginFill(0x990000,100);
flame.graphics.drawCircle(4,10,4);
flame.graphics.endFill();
centerX = stage.stageWidth/2;
centerY = stage.stageHeight/2;
radius = stage.stageWidth*.3;
flameRadius = radius + (ship.height*1.5);
ball.x = centerX + radius*Math.cos(THETA);
ball.y = centerY + radius*Math.sin(THETA);
flame.x = centerX + flameRadius*Math.cos(THETA);
flame.y = centerY + flameRadius*Math.sin(THETA);
_bitmap = new BitmapData(stage.stageWidth,stage.stageHeight,true,0xff000000);
_buffer = new BitmapData(stage.stageWidth,stage.stageHeight,true,0x00000000);
_buffer_clear = new BitmapData(stage.stageWidth,stage.stageHeight,true,0xff000000);
_blur_layer = new BitmapData(stage.stageWidth,stage.stageHeight,true,0xff000000);
_image = new Bitmap(_bitmap);
addChild(_image);
stage.addEventListener(KeyboardEvent.KEY_DOWN,setDirection);
stage.addEventListener(KeyboardEvent.KEY_UP,unsetDirection);
addEventListener(Event.ENTER_FRAME,moveMe);
}
public function moveMe(e:Event):void {
THETA += SPEED * xDirection;
ball.x = centerX + radius*Math.cos(THETA);
ball.y = centerY + radius*Math.sin(THETA);
flame.x = centerX + flameRadius*Math.cos(THETA);
flame.y = centerY + flameRadius*Math.sin(THETA);
var r:Number = Math.atan2(centerY - ball.y , centerX - ball.x)*RAD_DEG;
ball.rotation = r;
flame.rotation = r;
_blur_layer.draw(flame,flame.transform.matrix);
_blur_layer.applyFilter(_blur_layer,_blur_layer.rect,new Point(),_blurFilter);
_buffer.draw(_blur_layer);
_buffer.draw(ball,ball.transform.matrix);
_bitmap.draw(_buffer);
}
public function setDirection(e:KeyboardEvent):void {
switch(e.keyCode) {
case Keyboard.LEFT :
xDirection = -1;
break;
case Keyboard.RIGHT :
xDirection = 1;
break;
default:
break;
}
}
public function unsetDirection(e:KeyboardEvent):void {
xDirection = 0;
}
}
}
Every day, I am a little more comfortable with the new version of Actionscript. Conceptually, it is easier to grasp than was AS2, and of course it is much cleaner code than AS1.
I still have a few details to iron out with the keyboard events; they are more precise than I am used to.
As a side note, today I figured out how to change the default display for the (ugly to the point of being unusable) command prompt in Windows: launch cmd.exe, then right-click anywhere on the window chrome, and select "defaults". Now I have a 1000px wide window with a black background displaying 12-point green lucida console text. Much easier to read than that gray VIC-20 - esque default style.
| |
 Double Buffering in Actionscript 3 |
Posted - 6/21/2007 2:01:30 PM | I have just figured out how to implement Double Buffering in Actionscript 3. Here is the code for a recent experiment:
package {
import flash.display.*;
import flash.events.Event;
import flash.geom.*;
import flash.filters.BlurFilter;
[SWF(backgroundColor="0x000000",width="640",height="640",frameRate="50")]
public class PlasmaGhost extends Sprite {
private var _graphics:Array = new Array();
private var _bitmap:BitmapData;
private var _buffer:BitmapData;
private var _image:Bitmap;
private var _blurFilter:BlurFilter;
private var centerX:Number;
private var centerY:Number;
private var c:Number = 4;
private var d:Number = 3;
private var radius:Number = 50;
private var theta:Number = 0;
private var steps:Number = 540;
private var stepSize:Number = Math.PI*2/steps;
private var particles:Number = 500;
private var placement:Number = Math.PI*2/particles;
private var copyRect:Rectangle;
private var copyPoint:Point;
public function PlasmaGhost() {
for(var i:Number=0;i<particles;i++) {
_graphics[i] = new Shape();
_graphics[i].graphics.beginFill(0x040404*(i%64),100);
_graphics[i].graphics.drawCircle(0,0,1);
_graphics[i].graphics.endFill();
}
_blurFilter = new BlurFilter();
addEventListener(Event.ENTER_FRAME,registerStage);
}
public function registerStage(event:Event):void {
if(stage) {
removeEventListener(Event.ENTER_FRAME,registerStage);
copyPoint = new Point(0, 0);
centerX = stage.stageWidth/2;
centerY = stage.stageHeight/2;
radius = Math.min(stage.stageWidth,stage.stageHeight)*.8/2;
for(var i:Number=0;i<particles;i++) {
_graphics[i].x = Math.random()*stage.stageWidth;
_graphics[i].y = Math.random()*stage.stageHeight;
}
_bitmap = new BitmapData(stage.stageWidth,stage.stageHeight,true,0xff000000);
_buffer = new BitmapData(stage.stageWidth,stage.stageHeight,true,0xff000000);
_image = new Bitmap(_bitmap);
addChild(_image);
addEventListener(Event.ENTER_FRAME,onEnterFrame);
}
}
private function onEnterFrame(event:Event):void {
for(var i:Number=0;i<particles;i++) {
_graphics[i].x = centerX + radius * (Math.cos(c/d*(theta * placement*i)) * Math.cos(theta + placement*i));
_graphics[i].y = centerY + radius * (Math.cos(c/d*(theta + placement*i)) * Math.sin(theta * placement*i));
_buffer.draw(_graphics[i],_graphics[i].transform.matrix);
}
_buffer.applyFilter(_buffer,_buffer.rect,new Point(),_blurFilter);
_bitmap.draw(_buffer);
theta +=stepSize;
}
}
}
There are three important parts to this: _buffer, _bitmap, and _image.
When the program loads, _bitmap is set to be the data source for _image. _image is the actual visible object on the screen. _buffer is used to hold the graphics in the array called _graphics as they are moved and re-drawn. At every iteration, each element in _graphics is updated according to the formula for the pattern, and then drawn to _buffer. Once _graphics has been iterated through, _buffer is copied onto _bitmap. Since _bitmap is the data source for _image, whatever is in _bitmap appears on the screen.
Some additional notes about this piece of code:
1. at the beginning there is a line of meta-data which is used to set the Stage width, height, background color, and frame rate. I use this line because I am using the MXMLC.exe command-line compiler - included with the free Flex SDK - to compile my code, so I can't set these parameters manually in the Flash CS3 IDE. If you are using the Flash CS3 IDE, you can remove this line.
2. In the constructor function there I call an event which, in effect, checks to see if the stage (base container for all displayed objects) has instantiated yet. I had to do this because I am finding that, at higher frame rates, the code executes and tries to begin drawing before the stage has instantiated. It is a minor bug fix, and useful to know in case the situation arises.
3. The formula I use to create the pattern is a variation on the Rose.
For more information about using the command-line compiler, see Senocular's tutorial.
| |
 Maybe a Seed Bank... |
Posted - 6/7/2007 11:15:38 PM | This is an idea I have been kicking around for a long time. You have to fill and maintain a seed bank in the face of the apocalypse.
For whatever reason, civilization as we know it is about to come to an end. You know it, you see it coming, and you try to warn people, but no-one will listen. So you take matters into your own hands.
The game will be broken into two parts: Pre and post- apocalypse. In the first half, you must go around the world | land | continent and gather seeds and nuts to stockpile for re-planting after the world ends. You must find a minimum variety of seeds, and a minimum quantity of each species.
Cue the apocalypse. You go inside and spend an entertaining x number of months/years protecting the seeds from those who would eat them...or you.
Finally the apocalypse is over. Time to go out into the barren wasteland and try to re-start the ecology. With luck, you still have enough seeds to help civilization get back on its feet.
There are many details I could throw in, like a time limit, being the countdown to the end of the world, then another countdown once you re-emerge, trying to get the first crop in before it is too late in the season, or before you starve.
Special treasures could include various kinds of fertilizers, or hydroponics equipment, or water reclamation facilities. The first half of the game could include simple economics as you try to fill out your collection by trading with the people around you.
I would want a lot of the content and landscape to be procedurally generated, just to keep it from being the same game every time. With the research I did for the game last year, it shouldn't be too difficult to set up a system of some kind.
| |
 The Time is Almost Upon Us |
Posted - 6/6/2007 10:23:08 PM | ...he says as he eagerly awaits the announcement of 4e6.
I have spent the past couple of months working my tail off. No time for extracurricular programming. Last week I dove into Actionscript 3, and this week I have most of a web service enabled, SOAP-using, kiosk-based, XML-driven application built. If the project doesn't crash and burn in the next two weeks, it should be out in the world by the end of the month.
Coincidentally, that seems to be roughly when 4e6 will begin. Superpig has said things are running a little late this year, so the deadline for completion may be extended into the Christmas holidays. Fine with me. I just want to start making a game again.
And maybe get a little farther than I did last year. 4e5 was, for me, completely derailed by (a) a new girlfriend, (b) a new job, and (c) a dead computer. I had a lot of notes, and quite a few disconnected class files, but nothing even remotely resembling a playable demo, or even a start screen. Perhaps this year I will be a little more disciplined with my time.
In the meantime, I will use this forum to post game ideas, in the hopes that I or someone who reads one of them will find the ideas of some use. And if I ever get around to programming a game again, I will post it here.
Anything I do will be in Flash 9, either browser-based or a standalone executable.
| |
|
| S | M | T | W | T | F | S | | | | | | 1 | 2 | 3 | 4 | 5 | | | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | | 22 | | 24 | 25 | | | | 29 | | | | | | | | |
OPTIONS
Track this Journal
ARCHIVES
November, 2009
March, 2009
January, 2009
November, 2008
October, 2008
September, 2008
August, 2008
July, 2008
February, 2008
December, 2007
November, 2007
October, 2007
September, 2007
July, 2007
June, 2007
March, 2007
February, 2007
January, 2007
December, 2006
August, 2006
July, 2006
June, 2006
|