Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 47 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
47
Dung lượng
818,94 KB
Nội dung
scrollPos.Y -= steps; if (scrollPos.Y < 0) scrollPos.Y = 0; } if (keyState.down) { scrollPos.Y += steps; if (scrollPos.Y > (127 - 19) * 32) scrollPos.Y = (127 - 19) * 32; } if (keyState.left) { scrollPos.X -= steps; if (scrollPos.X < 0) scrollPos.X = 0; } if (keyState.right) { scrollPos.X += steps; if (scrollPos.X > (127 - 25) * 32) scrollPos.X = (127 - 25) * 32; } //clear the ground //note that this is usually not needed when drawing //the game level but this example draws the whole buffer gfxSurface.Clear(Color.Black); //update and draw the tiles drawScrollBuffer(); //print scroll position gfxSurface.DrawString("Scroll " + scrollPos.ToString(), fontArial, Brushes.White, 0, 0); gfxSurface.DrawString("Sub-tile " + subtile.ToString(), fontArial, Brushes.White, 300, 0); //draw a rect representing the actual scroll area gfxSurface.DrawRectangle(Pens.Blue, 0, 0, 801, 601); gfxSurface.DrawRectangle(Pens.Blue, 1, 1, 801, 601); //update surface 170 Chapter 7 n Rendering a Dungeon Level pbSurface.Image = bmpSurface; } public void updateScrollBuffer() { //fill scroll buffer with tiles int tilenum, sx, sy; for (int x = 0; x<26; x++) for (int y = 0; y < 20; y++) { sx = (int)(scrollPos.X / 32) + x; sy = (int)(scrollPos.Y / 32) + y; tilenum = tilemap[sy * 128 + sx].tilenum; drawTileNumber(x, y, tilenum, COLUMNS); } } public void drawTileNumber(int x, int y, int tile, int columns) { int sx = (tile % columns) * 33; int sy = (tile / columns) * 33; Rectangle src = new Rectangle(sx, sy, 32, 32); int dx = x * 32; int dy = y * 32; gfxScrollBuffer.DrawImage(bmpTiles, dx, dy, src, GraphicsUnit.Pixel); } public void drawScrollBuffer() { //fill scroll buffer only when moving if (scrollPos != oldScrollPos) { updateScrollBuffer(); oldScrollPos = scrollPos; } //calculate sub-tile size subtile.X = scrollPos.X % 32; subtile.Y = scrollPos.Y % 32; Sub-Tile Scrolling 171 //create the source rect Rectangle source = new Rectangle((int)subtile.X, (int)subtile.Y, bmpScrollBuffer.Width, bmpScrollBuffer.Height); //draw the scroll viewport gfxSurface.DrawImage(bmpScrollBuffer, 1, 1, source, GraphicsUnit.Pixel); } private void Form1_KeyDown(object sender, KeyEventArgs e) { switch (e.KeyCode) { case Keys.Up: case Keys.W: keyState.up = true; break; case Keys.Down: case Keys.S: keyState.down = true; break; case Keys.Left: case Keys.A: keyState.left = true; break; case Keys.Right: case Keys.D: keyState.right = true; break; } } private void Form1_KeyUp(object sender, KeyEventArgs e) { switch (e.KeyCode) { case Keys.Escape: 172 Chapter 7 n Rendering a Dungeon Level Application.Exit(); break; case Keys.Up: case Keys.W: keyState.up = false; break; case Keys.Down: case Keys.S: keyState.down = false; break; case Keys.Left: case Keys.A: keyState.left = false; break; case Keys.Right: case Keys.D: keyState.right = false; break; } } private void loadTilemapFile(string filename) { try { XmlDocument doc = new XmlDocument(); doc.Load(filename); XmlNodeList nodelist = doc.GetElementsByTagName("tiles"); foreach (XmlNode node in nodelist) { XmlElement element = (XmlElement)node; int index = 0; int value = 0; string data1 = ""; bool collidable = false; //read tile index # Sub-Tile Scrolling 173 index = Convert.ToInt32(element.GetElementsByTagName( "tile")[0].InnerText); //read tilenum value = Convert.ToInt32(element.GetElementsByTagName( "value")[0].InnerText); //read data1 data1 = Convert.ToString(element.GetElementsByTagName( "data1")[0].InnerText); //read collidable collidable = Convert.ToBoolean(element. GetElementsByTagName("collidable")[0].InnerText); tilemap[index].tilenum = value; tilemap[index].data1 = data1; tilemap[index].collidable = collidable; } } catch (Exception es) { MessageBox.Show(es.Message); } } private void Form1_FormClosed(object sender, FormClosedEventArgs e) { bmpSurface.Dispose(); pbSurface.Dispose(); gfxSurface.Dispose(); bmpScrollBuffer.Dispose(); gfxScrollBuffer.Dispose(); fontArial.Dispose(); timer1.Dispose(); } } } 174 Chapter 7 n Rendering a Dungeon Level Level Up! Wow, that was a ton of great information and some killer source code! This gives us enough information to begin working on the dungeon levels! I don’t know about you, but after this long wait, it feels good to have reached this point. Now that we have a level editor and a working level renderer, we can begin working on gameplay. Although the tilemap is drawing, we aren’t using any of the extended data fields (such as Collidable), which is the topic of the next two chapters! Also, we have that really great Game class back in Chapter 3 that will be more useful than the clunky Timer,sowe’ll go to full-time use of the Game class and a while loop in the next chapter. Speaking of which, Chapter 8 is about adding objects to the dungeon and simulating lighting by hiding or showing things based on their distance from the player! Level Up! 175 This page intentionally left blank Adding Objects to the Dungeon In this chapter we will learn how to add objects to the game world in such a way that they will show up when the viewport scrolls. This will require some coding trickery that goes a bit beyond the usual fare that we’ve needed so far, so if your experience with the C# language is somewhat on the light side, you will want to pay close attention to the explanations here. We will go back to using the Game class that was first introduced back in Chapter 2, “Drawing Shapes and Bitmaps with GDI+,” which handles most of the “framework” code needed for a game that has been put on hold while building the level editor and testing out game levels. But now we can return to the Game class, as well as the Sprite class from Chapter 3, “Sprites and Real-Time Animation.” Here are the goods: n Adding scenery to the game world n A new game loop n Level class n Adding trees n Adding an animated character Chapter 8 177 Adding Objects to the Game World Our game level editor works great for creating tilemaps, and it has support for additional data fields and a collision property. But, there comes a point when you need more than just the tilemap data to make a real game—you need interactive objects in the game world as well. So, the first thing we’re going to learn in this chapter is how to add some scenery objects, using the tilemap scrolling code developed in the previous chapter. At the same time, we need to address performance. The scrolling code takes up 100% of the processor when the scroll buffer is being refilled continuously. Even if you move the scroll position one pixel, the entire buffer is rebuilt. That is consuming huge amounts of processor time! It might not even be noticeable on a typical multi-core system today, but a laptop user would definitely notice because that tends to use up the battery very quickly. In addition to adding scenery, we’ll work on a new core game loop that is more efficient. A New Game Loop If you open up the Sub-Tile Smooth Scroller project from the previous chapter, watch it run while looking at your processor’s performance in Task Manager. To open Task Manager, you can right-click the Windows toolbar and choose Start Task Manager, or you can press Ctrl+Alt+Delete to bring up the switch user screen to find Task Manager. Figure 8.1 shows Task Manager while the aforementioned demo is running. Note how one of the cores is pretty much maxed out while the others are idle—that’s because the program is running in just one thread, and it’s pushing the processor pretty hard for such a seemingly simple graphics demo. The reason for this processor abuse is the use of a timer for rendering. For reference, here is a cropped version of the timer1_tick() function from the previous chapter. private void timer1_tick(object sender, EventArgs e) { int steps = 4; if (keyState.up) { scrollPos.Y -= steps; if (scrollPos.Y < 0) scrollPos.Y = 0; 178 Chapter 8 n Adding Objects to the Dungeon } } The timer event began firing when the timer1 object was created via this code in Form1_Load: //create the timer timer1 = new Timer(); timer1.Interval = 20; timer1.Enabled = true; timer1.Tick += new EventHandler(timer1_tick); Figure 8.1 Observing processor utilization in Task Manager. Adding Objects to the Game World 179 [...]... Sprite hero; int heroDir = 0; public Form1() { InitializeComponent(); } private void Form1_FormClosed(object sender, FormClosedEventArgs e) { gameover = true; } private void Form1_Load(object sender, EventArgs e) { this.Text = "Walk About Demo"; //create game object Form form = (Form)this; game = new Game( ref form, 800, 600); //create tilemap level = new Level(ref game, 25, 19, 32); level.loadTilemap("sample.level");... void Form1_FormClosed(object sender, FormClosedEventArgs e) { gameover = true; } private void Form1_Load(object sender, EventArgs e) { gameover = false; treesVisible = 0; drawLast = 0; this.Text = "Random Tree Demo"; //create game object 193 194 Chapter 8 n Adding Objects to the Dungeon Form form = (Form)this; game = new Game( ref form, 800, 600); //create tilemap level = new Level(ref game, 25, 19, 32);... Game World using using using using System.Drawing; System.Windows; System.Windows.Forms; System.Xml; namespace RPG { public partial class Form1 : Form { public struct keyStates { public bool up, down, left, right; } Game game; Level level; keyStates keyState; bool gameover; Bitmap treeImage; List trees; int treesVisible; int drawLast; public Form1() { InitializeComponent(); } private void Form1_FormClosed(object... for form controls (like the Timer as well as for drawing the controls) If we call it every frame, then our game loop will be even more limited than it was with the timer! No, we need to learn just when and where to use this function and that calls for a knowledge of frame-rate timing Do you recall the Game class from way back in Chapter 3? We will be using the Game class again in this chapter The Game. .. level.loadPalette("palette.bmp", 5) ; //load trees treeImage = game. LoadBitmap("trees64.png"); trees = new List(); for (int n = 0; n< 100;n++) { Sprite tree = new Sprite(ref game) ; tree.Image = treeImage; tree.Columns = 4; tree.TotalFrames = 32; tree.CurrentFrame = game. Random(31); tree.Size = new Size(64, 64); tree.Position = new PointF (game. Random(1000), game. Random(1000)); trees.Add(tree); } while (!gameover) {... freeze up the form Fortunately, there’s a function that will do all of the events: Application.DoEvents() This code can be added to the end of Form1_Load so it’s the last thing that runs after everything has been loaded for the game: while (!gameover) { doUpdate(); } Application.Exit(); Reviewing Game. cs Somewhere in that doUpdate() function, we have to call Application.DoEvents() so the form can be... an Animated Character Figure 8 .5 An animated character now walks in the direction that the scroller is moving using System.Windows.Forms; using System.Xml; namespace RPG { public partial class Form1 : Form { public struct keyStates { public bool up, down, left, right; } Game game; Level level; keyStates keyState; 199 200 Chapter 8 n Adding Objects to the Dungeon bool gameover = false; Bitmap treeImage;... engine for a highspeed game loop! Timers are more often used to fire off signals at regular intervals for hardware devices, to monitor a database for changes, that sort of thing It does not have very good granularity, which means precision at high speed So, we need to replace the timer with our own real-time loop I’ve got just the thing—a while loop But, Visual C# programs are graphical and formsbased,... level.loadPalette("palette.bmp", 5) ; //load trees treeImage = game. LoadBitmap("trees64.png"); trees = new List(); for (int n = 0; n< 100;n++) { Sprite tree = new Sprite(ref game) ; tree.Image = treeImage; tree.Columns = 4; tree.TotalFrames = 32; tree.CurrentFrame = game. Random(31); Adding an Animated Character tree.Size = new Size(64, 64); tree.Position = new PointF (game. Random(1000), game. Random(1000));... int frameRate = game. FrameRate(); //drawing code should be limited to 60 fps int ticks = Environment.TickCount; if (ticks > drawLast + 16) { drawLast = ticks; //draw the tilemap level.Draw(0, 0, 800, 600); //draw the trees in view drawTrees(); //print da stats game. Print(0, 0, "Scroll " + level.ScrollPos.ToString()); game. Print( 250 , 0, "Frame rate " + frameRate.ToString()); game. Print (50 0, 0, "Visible . scenery to the game world n A new game loop n Level class n Adding trees n Adding an animated character Chapter 8 177 Adding Objects to the Game World Our game level editor works great for creating. just the thing—a while loop. But, Visual C# programs are graphical and forms- based, so we can’t just make a loop and do what we want, because that will freeze up the form. Fortunately, there’s a function. added to the end of Form1_Load so it’s the last thing that runs after everything has been loaded for the game: while (!gameover) { doUpdate(); } Application.Exit(); Reviewing Game. cs Somewhere