/** * We must set the screen dimensions to something */ final int BLOCK = 16, screenWidth = 32*BLOCK, screenHeight = 27*BLOCK; /** * Gravity consists of a uniform downforce, * and a gravitational acceleration */ float DOWN_FORCE = 2; float ACCELERATION = 1.3; float DAMPENING = 0.75; /** * initializing means building an "empty" * level, which we'll */ void initialize() { frameRate(30); reset(); } /** * Reset the entire game */ void reset() { clearScreens(); addScreen("MainLevel", new MainLevel(4*width, height)); } /** * Our "empty" level is a single layer * level, doing absolutely nothing. */ class MainLevel extends Level { MainLevel(float levelWidth, float levelHeight) { super(levelWidth, levelHeight); addLevelLayer("background layer", new MainBackgroundLayer(this)); addLevelLayer("main layer", new MainLevelLayer(this)); setViewBox(0,0,screenWidth,screenHeight); // And of course some background music! SoundManager.load(this, "audio/bg/Overworld.mp3"); SoundManager.play(this); } } /** * Our main level layer has a background * color, and nothing else. */ class MainBackgroundLayer extends LevelLayer { Mario mario; MainBackgroundLayer(Level owner) { super(owner, owner.width, owner.height, 0,0, 0.75,0.75); setBackgroundColor(color(0, 100, 190)); addBackgroundSprite(new TilingSprite(new Sprite("graphics/backgrounds/sky_2.gif"),0,0,width,height)); } void draw() { super.draw(); } } /** * Our main level layer has a background * color, and nothing else. */ class MainLevelLayer extends LevelLayer { Mario mario; MainLevelLayer(Level owner) { super(owner); //setBackgroundColor(color(0,130,255)); addBackgroundSprite(new TilingSprite(new Sprite("graphics/backgrounds/sky.gif"),0,0,width,height)); // set up Mario! mario = new Mario(); mario.setPosition(32, height-64); addPlayer(mario); // add a few slanted hills addSlant(256, height-48); addSlant(1300, height-48); addSlant(1350, height-48); // we don't want mario to walk off the level, // so let's add some side walls addBoundary(new Boundary(-1,0, -1,height)); addBoundary(new Boundary(width+1,height, width+1,0)); // add general ground addGround("ground", -32,height-48, -32 + 17*32,height); addBoundary(new Boundary(-32 + 17*32,height-48,-32 + 17*32,height)); addInteractor(new Muncher(521, height-8)); addInteractor(new Muncher(536, height-8)); addInteractor(new Muncher(552, height-8)); addInteractor(new Muncher(567, height-8)); addBoundary(new Boundary(-31 + 19*32,height,-31 + 19*32,height-48)); addGround("ground", -31 + 19*32,height-48, width+32,height); // add some ground platforms addGroundPlatform("ground", 928, height-224, 96, 112); addCoins(928,height-236,96); addGroundPlatform("ground", 920, height-176, 32, 64); addGroundPlatform("ground", 912, height-128, 128, 80); addCoins(912,height-140,128); addGroundPlatform("ground", 976, height-96, 128, 48); addGroundPlatform("ground", 1442, height-128, 128, 80); addCoins(1442,height-140,128); addGroundPlatform("ground", 1442+64, height-96, 128, 48); // mystery coins addForPlayerOnly(new DragonCoin(352,height-164)); // Let's also add a koopa on one of the slides Koopa koopa = new Koopa(264, height-178); addInteractor(koopa); // add lots of just-in-time triggers addTriggers(); // and let's add the thing that makes us win! addGoal(1920, height-48); } /** * Add some ground. */ void addGround(String tileset, float x1, float y1, float x2, float y2) { TilingSprite groundline = new TilingSprite(new Sprite("graphics/backgrounds/"+tileset+"-top.gif"), x1,y1,x2,y1+16); addBackgroundSprite(groundline); TilingSprite groundfiller = new TilingSprite(new Sprite("graphics/backgrounds/"+tileset+"-filler.gif"), x1,y1+16,x2,y2); addBackgroundSprite(groundfiller); addBoundary(new Boundary(x1,y1,x2,y1)); } /** * This creates the raised, angled sticking-out-ground bit. * it's actually a sprite, not a rotated generated bit of ground. */ void addSlant(float x, float y) { Sprite groundslant = new Sprite("graphics/backgrounds/ground-slant.gif"); groundslant.align(LEFT, BOTTOM); groundslant.setPosition(x, y); addBackgroundSprite(groundslant); addBoundary(new Boundary(x, y + 48 - groundslant.height, x + 48, y - groundslant.height)); } /** * Add a platform with solid ground underneath. */ void addGroundPlatform(String tileset, float x, float y, float w, float h) { // top layer Sprite lc = new Sprite("graphics/backgrounds/"+tileset+"-corner-left.gif"); lc.align(LEFT, TOP); lc.setPosition(x, y); Sprite tp = new Sprite("graphics/backgrounds/"+tileset+"-top.gif"); Sprite rc = new Sprite("graphics/backgrounds/"+tileset+"-corner-right.gif"); rc.align(LEFT, TOP); rc.setPosition(x+w-rc.width, y); TilingSprite toprow = new TilingSprite(tp, x+lc.width, y, x+(w-rc.width), y+tp.height); addBackgroundSprite(lc); addBackgroundSprite(toprow); addBackgroundSprite(rc); // sides/filler TilingSprite sideleft = new TilingSprite(new Sprite("graphics/backgrounds/"+tileset+"-side-left.gif"), x, y+tp.height, x+lc.width, y+h); TilingSprite filler = new TilingSprite(new Sprite("graphics/backgrounds/"+tileset+"-filler.gif"), x+lc.width, y+tp.height, x+(w-rc.width), y+h); TilingSprite sideright = new TilingSprite(new Sprite("graphics/backgrounds/"+tileset+"-side-right.gif"), x+w-rc.width, y+tp.height, x+w, y+h); addBackgroundSprite(sideleft); addBackgroundSprite(filler); addBackgroundSprite(sideright); // boundary to walk on addBoundary(new Boundary(x, y, x+w, y)); } // add coins over a horizontal stretch void addCoins(float x, float y, float w) { float step = 16, i = 0, last = w/step; for(i=0; i<last; i++) { addForPlayerOnly(new Coin(x+8+i*step,y)); } } // And finally, the end of the level! void addGoal(float xpos, float hpos) { hpos += 1; // background post Sprite goal_b = new Sprite("graphics/assorted/Goal-back.gif"); goal_b.align(CENTER, BOTTOM); goal_b.setPosition(xpos, hpos); addBackgroundSprite(goal_b); // foreground post Sprite goal_f = new Sprite("graphics/assorted/Goal-front.gif"); goal_f.align(CENTER, BOTTOM); goal_f.setPosition(xpos+32, hpos); addForegroundSprite(goal_f); // the finish line rope addForPlayerOnly(new Rope(xpos, hpos-16)); } // In order to effect "just-in-time" sprite placement, // we set up some trigger regions. void addTriggers() { addTrigger(new KoopaTrigger(412,0,5,height, 350, height-64, -0.2, 0)); addTrigger(new KoopaTrigger(562,0,5,height, 350, height-64, -0.2, 0)); addTrigger(new KoopaTrigger(916,0,5,height, 350, height-64, -0.2, 0)); } void draw() { super.draw(); viewbox.track(parent, mario); // just in case! if(mario!=null && mario.active != null && mario.active.name!="dead" && mario.y>height) { reset(); } } } ////////////// // ACTORS // ////////////// /** * Our dapper mustachioed hero */ class Mario extends Player { int score = 0; float speed = 2; Mario() { super("Mario"); setStates(); handleKey('W'); handleKey('A'); handleKey('S'); handleKey('D'); setImpulseCoefficients(DAMPENING, DAMPENING); setForces(0, DOWN_FORCE); setAcceleration(0, ACCELERATION); } /** * Set up our states */ void setStates() { // idling state addState(new State("idle", "graphics/mario/small/Standing-mario.gif")); // running state addState(new State("running", "graphics/mario/small/Running-mario.gif", 1, 4)); // dead state O_O State dead = new State("dead", "graphics/mario/small/Dead-mario.gif", 1, 2); dead.setAnimationSpeed(0.25); dead.setDuration(100); addState(dead); SoundManager.load(dead, "audio/Dead mario.mp3"); // jumping state State jumping = new State("jumping", "graphics/mario/small/Jumping-mario.gif"); jumping.setDuration(15); addState(jumping); SoundManager.load(jumping, "audio/Jump.mp3"); // victorious state! State won = new State("won", "graphics/mario/small/Standing-mario.gif"); won.setDuration(240); addState(won); // default: just stand around doing nothing setCurrentState("idle"); } /** * Handle input */ void handleInput() { // we don't handle any input when we're dead~ if(active.name=="dead" || active.name=="won") return; // what do we "do"? (i.e. movement wise) if(isKeyDown('A') || isKeyDown('D')) { if (isKeyDown('A')) { // when we walk left, we need to flip the sprite setHorizontalFlip(true); // walking left means we get a negative impulse along the x-axis: addImpulse(-speed, 0); // and we set the viewing direction to "left" setViewDirection(-1, 0); } if (isKeyDown('D')) { // when we walk right, we need to NOT flip the sprite =) setHorizontalFlip(false); // walking right means we get a positive impulse along the x-axis: addImpulse(speed, 0); // and we set the viewing direction to "right" setViewDirection(1, 0); } } // if the jump key is pressed, and we're standing on something, // let's jump! if(isKeyDown('W') && active.name!="jumping" && boundaries.size()>0) { // generate a massive impulse upward addImpulse(0,-35); // and make sure we look like we're jumping, too setCurrentState("jumping"); SoundManager.play(active); } // and what do we look like when we do this? if (active.mayChange()) { // if we're not jumping, but left or right is pressed, // make sure we're using the "running" state. if(isKeyDown('A') || isKeyDown('D')) { setCurrentState("running"); } // if we're not actually doing anything, // then we change the state to "idle" else { setCurrentState("idle"); } } } /** * When 'fixed frame count' animations end * such as the jump animation, what should we do? */ void handleStateFinished(State which) { if(which.name == "dead" || which.name == "won") { removeActor(); reset(); } else { setCurrentState("idle"); } } /** * What happens when we touch another actor? */ void overlapOccurredWith(Actor other, float[] direction) { if (other instanceof Koopa) { Koopa koopa = (Koopa) other; float angle = direction[2]; // We bopped a koopa on the head! float tolerance = radians(75); if (PI/2 - tolerance <= angle && angle <= PI/2 + tolerance) { koopa.squish(); stop(0,0); setImpulse(0, -30); setCurrentState("jumping"); } // Oh no! We missed and touched a koopa! else { die(); } } } /** * When we die, we need to go in the funky "oh no we lost~" dance dive. */ void die() { setCurrentState("dead"); setInteracting(false); addImpulse(0,-30); setForces(0,3); // stop background music! SoundManager.stop(getLevelLayer().getLevel()); // play sad music =( SoundManager.play(active); } /** * What happens when we get pickups? */ void pickedUp(Pickup pickup) { // we got some points if (pickup.name=="Regular coin") { score++; } // we got big points else if (pickup.name=="Dragon coin") { score+=100; } // we won! else if (pickup.name=="Finish line") { setCurrentState("won"); } } } /** * Our main enemy in this game */ class Koopa extends Interactor { Koopa(float x, float y) { super("Koopa Trooper"); setStates(); setForces(-0.25, DOWN_FORCE); setImpulseCoefficients(DAMPENING, DAMPENING); setPosition(x,y); } /** * Set up our states */ void setStates() { // walking state State walking = new State("idle", "graphics/enemies/Red-koopa-walking.gif", 1, 2); walking.setAnimationSpeed(0.12); SoundManager.load(walking, "audio/Squish.mp3"); addState(walking); // if we get squished, we first get naked... State naked = new State("naked", "graphics/enemies/Naked-koopa-walking.gif", 1, 2); naked.setAnimationSpeed(0.12); SoundManager.load(naked, "audio/Squish.mp3"); addState(naked); setCurrentState("idle"); } /** * when we hit a vertical wall, we want our * koopa to reverse direction */ void gotBlocked(Boundary b, float[] intersection) { if (b.x==b.xw) { fx = -fx; setHorizontalFlip(fx > 0); } } void squish() { SoundManager.play(active); // do we have our shell? Then we only get half-squished. if (active.name != "naked") { setCurrentState("naked"); return; } // no shell... this koopa is toast. removeActor(); } } /** * The muncher plant. To touch it is to lose. */ class Muncher extends Interactor { Muncher(float x, float y) { super("Muncher"); setPosition(x,y); setupStates(); } void setupStates() { State munch = new State("munch","graphics/enemies/Muncher.gif", 1, 2); munch.setAnimationSpeed(0.20); addState(munch); } void overlapOccurredWith(Actor other, float[] overlap) { super.overlapOccurredWith(other, overlap); if (other instanceof Mario) { ((Mario)other).die(); } } } ///////////////// // PICKUPS // ///////////////// /** * All pickups in Mario may move, and if * they do, they will bounce when hitting * a clean boundary. */ class MarioPickup extends Pickup { MarioPickup(String name, String spritesheet, int rows, int columns, float x, float y, boolean visible) { super(name, spritesheet, rows, columns, x, y, visible); } void gotBlocked(Boundary b, float[] intersection) { if (intersection[0]-x==0 && intersection[1]-y==0) { fx = -fx; active.sprite.flipHorizontal(); } } } /** * A regular coin */ class Coin extends MarioPickup { Coin(float x, float y) { super("Regular coin", "graphics/assorted/Regular-coin.gif", 1, 4, x, y, true); SoundManager.load(this, "audio/Coin.mp3"); } void pickedUp() { SoundManager.play(this); } } /** * A dragon coin! */ class DragonCoin extends MarioPickup { DragonCoin(float x, float y) { super("Dragon coin", "graphics/assorted/Dragon-coin.gif", 1, 10, x, y, true); SoundManager.load(this, "audio/Dragon coin.mp3"); } void pickedUp() { SoundManager.play(this); } } /** * The finish line is also a pickup, * and will trigger the "clear" state * for the level when picked up. */ class Rope extends MarioPickup { Rope(float x, float y) { super("Finish line", "graphics/assorted/Goal-slider.gif", 1, 1, x, y, true); Sprite s = getState("Finish line").sprite; s.align(LEFT, TOP); s.setNoRotation(true); s.addPathLine(0, 0, 1, 1, 0, 0, -116, 1, 1, 0, 50); s.addPathLine(0, -116, 1, 1, 0, 0, 0, 1, 1, 0, 50); s.setLooping(true); SoundManager.load(this, "audio/bg/Course-clear.mp3"); } void pickedUp() { // stop background music! SoundManager.stop(getLevelLayer().getLevel()); // play victory music! SoundManager.play(this); } } //////////////// // TRIGGERS // //////////////// /** * triggers a koopa trooper 350px to the right */ class KoopaTrigger extends Trigger { float kx, ky, fx, fy; KoopaTrigger(float x, float y, float w, float h, float _kx, float _ky, float _fx, float _fy) { super("koopa", x, y, w, h); kx = _kx; ky = _ky; fx = _fx; fy = _fy; } void run(LevelLayer layer, Actor actor, float[] intersection) { Koopa k = new Koopa(x+kx, ky); if (fx>0) { k.setHorizontalFlip(true); } layer.addInteractor(k); // remove this trigger so that it's not repeated removeTrigger(); } }
Comments
Post a Comment