Pages: 1
Topic closed
I've recently been getting multiple requests for explanations on how Capital was made so I've typed up journal notes and put them with the game Project Notes here: http://scratch.mit.edu/projects/jmgibson/7644
There are some tricks and tips in that document that some inobvious how-tos there too:
----------------------------------------------------------------------------------------------------------
Making Capital by jmgibson
First I'd like to thank you for your interest. Its an appreciated and heartfelt compliment.
The reason Capital happened was that I was seeing a lot of discussion on the Scratch site about not being able to do scrolling backgrounds. Brad, a coworker and friend of mine was working out a different Scratch game of his and pointed out the issue to me. And I said to myself, "Self, we've done about as many systems for scrolling backgrounds as McDonalds has served hamburgers so let's give it a shot and work out the details." And I agreed with myself with "Yes and maybe it will help someone later." So given that inspiration, me (who was a late-comer to the conversation), myself, and I got started on the viewing system with the rough idea of a video game to come later. That's why its named Capital_v0.0, as the intention was to do just a scrolling example first and then move into gameplay, and the v0.0 just stuck.
The design of the scroller was inspired by the upright video games of the 1970's and 1980's; thus the rich and overbright color scheme. My favourite game design of that era was Sinistar, which fit into the rough framework of how I wanted the viewing system to work. Actually most of Capital was inspired by the Sinistar concept of "collect enough resources to kill the bad guy over and over again." I just haven't gotten to the "collection" part yet as people seem to be enjoying themselves regardless.
Here is the order of the system's creation just so you can see the topics we'll cover, historically speaking:
Viewing and the tiling backgounds
Event synchronization
World Coordinate Space
Ship motion and movement controls
Making shipMain's Graphics
Entering the game
Making the background tiles wrap
Missile motion and 2D inertial dynamics
The bad guy and his control "logic"
Getting hit by the missile
Shooting the missile
Shooting the bad guy
The sounds and music
Warp control
Lidar Display
Game over and intro screens
Viewing and the Tiling Backgounds
This is where I started. First know that you can't do the obvious thing and display a larger-than-the-view background image on the Stage object. That makes sense for the original intentions of Scratch but complicates our system. That means that if we want to use bitmap imagery as our scroller source we have to use sprites for it.
So first I tried using a very very large sprite. But Scratch wouldn't let the image be larger than the viewport. BUT I discovered that you were allowed to translate a smaller image OFF the viewport. So I diced up my original background image into sixteen tiles, each of which would fit well inside the viewport, and made them all sprites: bg_0_0 to bg_3_3 where the numbers are the 2D indices to the tile position.
Now THAT is inefficient: drawing sixteen large sprites every single frame. So I added a conditional in each sprite's self-handler that hides the tile if it gets too far away from the center of the view.
Event Synchronization
Next I wanted to flyover the tiles in the Sprite engine to see how well they drew so I had to build a videogame-like event loop. So when the game starts the Stage is in charge of directing the whole deal. It should be the only place you'll see an "On-Flag" event handler in the whole of Capital. That (eventually) generates a "start" event which preps the viewport viewing system, initializes all the sprites and on the Stage goes into a "forever" event loop that broadcasts a sync'd "update" event to all the Sprites and manages the viewport. At the end of each update it waits a tiny amount of time in order for all the asynchronous event handlers (playing sounds, handling key-presses, "hit" animations, that sort of thing) time to hopefully catch up. This forever loop terminates when the sim part of the game exits exits with anything that broadcasts an "end" event.
Event syncing will be very important later onces a ton of stuff starts happening "at-once" so we may as well build the event loop here.
World Coordinate Space
Here is where I stopped using the viewport coordinate space for viewing (http://en.wikipedia.org/wiki/Coordinate_space) and started using a "world" definition. On the first frame of the simulation the two share the same origin. That is the viewport's [x=0,y=0] is coindicent with the game simulation's [0,0]. After that, though they differ. For those of us not familiar with the freedom that representing viewing and motion with different coordinate spaces here is the essense of what happens.
The center of your viewport coordinate space is always [0,0] and always right at the center of the rectangle representing your viewport, just as Scratch would have it. A world coordinate space is differnt. This is one where [0,0] is just where the game starts. But events can be taking place far away from that "origin". And since you want to "see" those events you have to drag the area of the world, kicking and screaming, back in front of the viewport. I don't really want to go into what would happen if we were attempting to represent rotating, skewing or scaling worlds but for a 2D translation (motion) engine like this one, putting one space (world) back into the space of another (the viewport) its a simple subtraction of the X and Y of the center of where we want to look in the world from any world space coordinate we want to represent.
e.g. If my ship is at [X,Y] = [100,1300] and I want my viewpoint to be looking near the ship at ViewCenter = [101,1290], my viewport space coordinate for the ship is [100,1300] - [101,1290] = [-1, 10] which, tada, is near the center of the viewport.
Once you have this working your hands are virtually free. All you have to do is keep track of where you want to be looking in the world space separately than the view space and the world events. The "camera", then, or the View Center is now sort of like its own sprite but represents not what you see but where you look in the world to see other stuff.
You'll notice that when you enter the game or when you warp, the ViewCenter starts to play a game of catchup with the ship. The ship and viewCenter positions are different and though I deliberately try to follow the ship, I wanted the viewCenter to act like a cameraman at a sports event: always trying to catch up to and center the action, but never quite doing it. The way this happens is I make the ship world coordinates (from now on I'll only be talking terms of world coordinates) global variables and the Screen object figures out the difference between the ship coordinates and the viewCenter coordinates. Then it moves the viewCenter 0.15 (15 percent) of the way to the ship. If the ship stays still long enough the viewCenter will always eventually catch up to it.
Every sprite in the game, except for the lidar and score displays, is expressed in world coordinates and is transformed back into viewport space as a final, secondary process during its update.
Ship Motion and Movement Controls
Here is where I ran into another problem that required a creative solution. There's no trig or linear algebra built into Scratch. This means that even though you know your facing in the world, can't effectively record your velocity or acceleration in X and Y separately factoring in your facing. So though you can change your angular velocity on your sprite pretty easily by keeping track of its rotational velocity and adding that to the facing angle on every update, if you try to try to accelerate along that facing there isn't an obvious way to decide what your new velocities and positions are in world space X and Y. This was trillian's first question. How am I getting world space forward and revere thrust.
Fortunately there is linear vector algebra built into Scratch. It is hidden from you but its there. When you "move 10 steps" internally in viewport space, Scratch is converting your sprite's facing to a vector and scaling that vector to be the length of the amount by which you want to move your sprite foward. We can take advantage of that. I use that behaviour. Let's say I want to compute a new velocity in X and Y from an old velocity, a facing and a certain amount of thrust (or acceleration):
1) Hide your sprite
2) Move your sprite to Scratch's (the viewport's) origin [0,0].
2) Face your sprite in the direction you want to thrust
3) Move your sprite (foward is just positive amounts and backward is just negative amounts) by the amount that you want to accelerate.
4) The sprite's Scratch X and Y are now your new acceleration values. They don't mean anything else so store them and move on.
5) Add these X and Y to your existing sprite's X and Y velocity that you can store in either global or spite-local variables. That's your new componentized (X and Y) velocity.
Now when you update the position of your ship in world space you just add the resulting velocity to the world position variables of the ship during the update to get your new position.
After all of the above, to make the "coasting to a stop" behaviour I scale down all velocities to 0.95 of their initial values for each update. This is of course not the normal behaviour of space ships, more the behaviour of a ship in a resistant medium, like a boat in water. But its more fun this way. And hey, its my game and I like it this way. Phttt!
The above technique is most-represented by the Missile sprite.
Making shipMain's Graphics
I am an expert Houdini user. shipMain took about 20 minutes to model, texture and light for both standard flight lighting frames and for autocannon fire lighting in Houdini and Mantra. I used the free "Appretice" full-featured version so I had to roto (erase) out the Houdini watermark image from the sprite graphics after the fact. I did the same with the explosion sprite animation and sometimes you will get glimpses of where you can see part of the H from Houdini left over in the bigger parts of the explosion.
Entering the Game
By now I was representing all of the motion of the ship with facing velocity and positional velocity so to bring the ship into the game (and eventually to warp) all I had to do was generate a fun random velocity for both and let the ship do what the ship was going to do in the simulation. This applies to both shipMan and the bad guy, shipEnemy.
Making the Background Tiles Wrap
Here is a forum link to a Photoshop script I set up for dicing up tiles and a rough description what I did here: http://scratch.mit.edu/forums/viewtopic.php?pid=2359#p2359
Once you bring the tiles in you treat them as the other sprites in world space. The diffence is that they are only shown (not hidden) when they are near the viewCenter and they only move when they are outside the viewing locale. And the only reason they move is so that you have a continuous and infinite image. If your image only covered world [-500,-500] to [500,500] and your ship was at world [1000,500] you would be over a black background. But if your ship were travelling to [1000,500] and you wanted to put a background there, you'd want to go grab the appropriate background for that area and put it there before the ship arrived. This I do during the update for each tile. I use a modulus on the world space coordiates of the viewCenter to find out, regardless of map iteration, what part of the whole map I'm over for that iteration, then I subtract off the coordinates of the begining of that map iteration in world space. That tells me where each tile is in world space for that viewing locale. And that is where they are displayed for that update.
Missile Motion and 2D Inertial Dynamics
As above all the dynamics of the missile are represented with componentized velocities. It, however does not use a facing velocity. Instead it just faces its target...sort of. What it really does is put itself into the space of its targe ship. That is, a space defined by the ship being at [0,0]. It then predicts which direction and how much it needs to thrust to compensate for its own velocity by moving itself (without chaging its world space position quite yet) in that ship space by its velocity. That tells it where its going to be relative to the ship on the next update. From THAT position it faces the ship at [0,0] and thrusts by a fraction of the distance between itself and the ship, shipMain. This way its always trying to thrust such that on the next update it will steer closer to its target.
The Bad Guy and His Control "Logic"
shipEnemy is terribly, embarrassingly simple. He takes advantage of the fact that the player is distracted by the missile and that he is its launch device which makes it look like he has enough confidence not to just get the heck out of your way when you come flying in, autocannon blazing. Nope, what he really does is what the player does. Whenever the player hits a key, shipEnemy hits the same key 2 seconds later with slightly different acceleration characteristics. Lame but effective. Why does this work? 1) The motion LOOKs intelligent. 2) The missile distracts the player from what is really happening and 3) the resulting motion is different because its a different ship with different motion properties.
Getting Hit by the Missile
Shooting the Missile
Shooting the Bad Guy
These are basically the same. Each shot and the missile are independent Spites. The autocannon shots move in a straight line in world space and the missile motion is discussed above. When the shipMain sees that it is within some small distance from the missile it fires off a "missleHit" broadcast which the missile Sprite picks up and updates the Score and prepares itself to fire again. The explosion sprite also gets the "missileHit" broadcast and starts animating at a random size and rotation at a fixed position in world space. The explosion sprite is separate because it can be on the screen and have different motion at the same time as anything that can cause an explosion. We get away with one explosion for the whole game. Cheap, of me, I know, but it works.
The Sounds and Music
One of my favourite things is sound design. I love the sounds. For this I ripped off a couple of acquaintances of mine who work on Battlestar Galactica. You can't hear much of Bear's music, other than the percussion but all the sound effects are taken from the pilot reel of Battlestar Galactica. There is a loop of sound effects and music that constantly plays during the sim. Other than that I play the Viper guns sound effect any time the autocannon is fired, the Viper launch sounds for the warp sound, the shuttle steering blasts for turns, another Viper launch for engine thrusts and a generic explosion sound for all explosions.
Warp Control
Warping is just the same thing that happens when you restart the game but with a random offset position relative to the current position and with a few fun graphics effects as it leaves its current position. The viewport's catching up to the new position of the ship adds a lot of drama to this effect and basically sells the whole idea. The effect is polished off with a cool "engine up" sound effect.
Lidar Display (a.k.a. the radar at the bottom)
Again, with everything stored and sim'd in world space this is SUPER SIMPLE. You just draw tiny sprites with locator lines over everything else in a new space which we can call "lidar" space. This is a space defined by placing the center of its view at shipMain's position and scaling that space down to fit most useful data onto the lidar view area. Each lidar sprite is a mirror of its mirror in the world and starts each update by using its coordinates. Everything, including the lidar frame graphics, is shifted down out of your primary viewport's frame center, down to the bottom-ish part of the viewport.
Game over and intro screens
Lastly, we keep track of the staging state by using global variables to remember if we are currently "running" the simulation and if we "canStart", as in start the simulation with the 's' key. The "game over" graphics (which are * right now and will change) and the intro screen are the same sprite as they are just different states of the same progression of staging. But I let the sim continue to run during the game over stage so that the music and sound effects can finish and the game doesn't have a jarring conclusion, animation wise, but instead coasts naturally to a stop. Otherwise we just use broadcast event to traverse each stage in the staging processes.
Offline
Capital should be put in a few Galleries:
Pushing the Limits http://scratch.mit.edu/galleries/70
Best of Scratch http://scratch.mit.edu/galleries/266
The iwikepie game gallery http://scratch.mit.edu/galleries/115
for example.
Last edited by kevin_karplus (2007-06-10 19:57:31)
Offline
Several times I have commented in the main Scratch area about how useful it would have been if Project Notes had been uploaded with a shared program. Although, by it's very nature, Scratch code is readable by all that is NOT the same thing as being UNDERSTANDABLE by all. (See my latest forum post about a plea for further documentation).
The notes you have posted here are very useful and I echo all those who have complimented you on your game. It is projects like yours that prove just how powerful Scratch can be, and how professional the results can be. I see it as being essential for the MIT team to support users of all abilities by providing more detailed guidance and tutorials for those who aspire to your levels of planning and skill.
Something I am sure they will be keen to do.
Offline
jnp wrote:
how do you put music on scratch ?
Jnp
You can click on the sounds tab of any sprite, then click import. Find an .mp3 or .wav file that you want, and click OK. Then, you can click on the 'Sound' tab of blocks and you can use the block you want.
[play sound ______] means that it plays the sound that you want, and then moves on to the next block.
[play sound _________ until done] means that it waits for the sound to finish before moving on.
[stop all sounds] speaks for itself.
The help pages on this website should help more.
Offline
Topic closed
Pages: 1