418383: การโปรแกรมเกม การบรรยายครั้งที่...
DESCRIPTION
418383: การโปรแกรมเกม การบรรยายครั้งที่ 4. ประมุข ขันเงิน. Tetris. สร้างโดย Alexey Pajinov โปรแกรมเมอร์ชาวรัสเซีย ในปี 1985. Tetris. กฎ ( จาก Wikipedia): มีเทโทรมิโนตกลงมาสู่พื้นของบอร์ดทีละอัน - PowerPoint PPT PresentationTRANSCRIPT
418383: การโปรแกรมเกม การบรรยายครงท 4
ประมข ขนเงน
Tetris
• สรางโดย Alexey Pajinov โปรแกรมเมอรชาว รสเซย ในป 1985
Tetris
• กฎ ( จาก Wikipedia):– มเทโทรมโนตกลงมาสพนของบอรดทละอน– ผเลนตองควบคมเทโทรมโนในตกไปกองทพกนเปน
แถวเตมซงไมมชองวางอยภายใน โดยการเลอนไปทาง ซายขวา หรอหมน 90 องศา
– เมอเกดแถวเตม บลอกของเทโทรมโนในแถวนนทงหมดจะหายไป
– เกมจะจบลงเมอเทโทรมโนกองทบกนจนลนบอรดบนหนาจอ
เทโทรมโน• รปทรงทเกดจากการเอาบลอกสอนมาตอกน• เรยกชอวา I, J, L, O, S, T, และ Z.
Screen ตางๆ ในเกม• Title Screen– ฉากไตเตล– คลาส TitleScreen
• Play Screen– ฉากเลนเกม– คลาส PlayScreen
• Game Over Screen– ฉากเกมจบ– คลาส GameOverScreen
โคดของเกมpublic class Tetris : GameLib.Game{ public Tetris() : base() { Content.RootDirectory = "Content"; Graphics.PreferredBackBufferWidth = 800; Graphics.PreferredBackBufferHeight = 600;
AddScreen(new Screens.TitleScreen(this)); AddScreen(new Screens.GameOverScreen(this)); AddScreen(new Screens.PlayScreen(this)); SwitchScreen(“Title");
Tetromino.InitializePrototypes(); }}
Transition Diagram ของเกม
Title
Play
Game Over
“ ”จบเกม
“ ”จบเกม
“
กลบ title”
“ ”เลนใหมกระดานลน
“”
เรมเกม
TITLE และ GAME OVER SCREEN
เราตองการ
มองวาสวนนเปนเกมเกมหนงเลย• สถานะของเกม– ตวเลอกทผเลนเลอกอยตอนน
• การจดการปฏสมพนธกบผใช– ปม ขน/ ลง เปลยนตวเลอกทผเลนเลอก (title)– ปม Enter ควรจะเปลยนหนาจอเปนหนาจออน (choices)
• การวาดภาพบนหนาจอ– เขยนชอเกม– เขยนตวเลอก และเขยนวงเลบกามปลอมรอบตวเลอกปจจบน
จรงๆ แลว...
• Game Over Screen กมลกษณะคลายๆ กน
ระบบเมน• หนาจอทงสองทำาหนาทเปนเมน– อำานวยความสะดวกใหผใชเลอกตวเลอกจากหลายตวเลอก
– ตวเลอกแตละตวนำาไปสหนาจออน• ทงสองหนาจอมหนาตาคลายๆ กน– ขอความขนาดใหญอยขางบน– ขอความแสดงตวเลอกขนาดเลกลงอยขางลาง
• เราควรจะเขยนคลาสทเกบพฤตกรรมทเหมอนๆกนของระบบเมนไว
• แลวจงซบคลาสมน เพอสรางหนาจอทงสอง
คลาส MenuScreen
• มขอมลทนยามตวเมน และสถานะภายในของมน– Title– Choices– ตวเลอกปจจบน
• จดการขอมลเขาทผใชปอน– เปลยนตวเลอกเมอผใชกดขนลง
• แตไมระบพฤตกรรมเมอผใชเลอกตวเลอก– ตรงนใหซบคลาสไประบเอาเอง
ฟลดทสำาคญในคลาส MenuScreen
• string title;– ขอความไตเตล
• string[] choices;– อะเรยของตวเลอกตางๆ
• int currentChoice;– หมายเลขของตวเลอกปจจบน
เมธอดสำาคญในคลาส MenuScreen
• public MenuScreen(GameLib.Game game, string name, string title, string titleFontName, string[] choices, string choiceFontName)– game = เกมทม screen นอย– name = ชอของ screen– title = ไตเตล– titleFontName = asset name ของ font ทใชพมพ title– choices = ตวเลอกตางๆ– choiceFontName = assert name ของ font ทใชพมพ choice
เมธอดสำาคญในคลาส MenuScreen
• public abstract void ChoiceSelected( int index, GameTime gameTime);– ถกเรยกเมอผใชกดปม Enter– index = หมายเลขของ choice ทถกเลอกอยในปจจบน– เปน abstract เพอใหซบคลาสมาเตมพฤตกรรมเพมเตม
Title Screenpublic class TitleScreen : MenuScreen { public TitleScreen(GameLib.Game game) : base( game, "title_menu", "Tetris", "Vera64", new string[] { "Play", "Quit" }, "Vera32") { } : :}
Title Screenpublic override void ChoiceSelected(
int index, GameTime gameTime){ switch (index) { case 0: Game.SwitchScreen(“Play"); break; case 1: Game.Exit(); break; default: break; }}
Game Over Screenpublic class GameOverScreen : MenuScreen{ public GameOverScreen(GameLib.Game game) : base( game, "GameOver", "Game Over", "Vera64", new string[] { "Play Again", "Return to Title", "Quit" },
"Vera32") { }
: :}
Game Over Screenpublic override void ChoiceSelected(int index, GameTime gameTime){ switch (index) { case 0: Game.SwitchScreen("Play"); break; case 1: Game.SwitchScreen("Title"); break; case 2: Game.Exit(); break; default: break; }}
ฟอนต• เกมใชฟอนต Bitstream Vera• ดาวนโหลดไดจาก http://www.gnome.org/fonts/• ใน TetrisContent ม Sprite Font ทสรางจาก
Bitstream Vera อยสองตว– Vera64
• Bitstream Vera Sans Mono ขนาด 64p• ใชเขยน title
– Vera32• Bitstream Vera Sans Mono ขนาด 32p• ใชเขยน choice
การจดการกบอนพตจาก KEYBOARD
จดการอนพตจากคยบอรด• กดลกศรขน ตวเลอกเลอนขน• กดลกศรลง ตวเลอกเลอนลง• เราจะจดการกบการกดปมอยางไร?
• อาจใช KeyboardState.IsKeyDown(Keys.Down) เพอตรวจวาปมลกศรลงถกกดหรอไม
• ถาจรงกเลอนตวเลอกลง• ทำาทำานองเดยวกนไดกบปมลกศรขน
• ปญหา: ตวเลอกถกเปลยนอยตลอดเวลาเวลากดคาง จนคนมองไมเหนความเปลยนแปลง
จดการอนพตจากคยบอรด• อกวธหนง: เชควาผใชเพงจะกดปมในเฟรมนหรอเปลา
• กลาวคอจำาไววาในเฟรมกอนกดหรอไม• ถาเฟรมกอนไมกด แตเฟรมนกด แสดงวาเพงจะกด
• ปญหา: ตวเลอกไมเปลยนถาผใชกดปมคาง
จดการอนพตจากคยบอรด• สงทเราตองการ: พฤตกรรมเวลาเราพมพขอความใน
text editor ทวๆ ไป• สมมตวาเราใช Notepad จะเกดอะไรขนถาคณกดปม a คางไว?– ‘ตวอกษร a’ ตวแรกจะปรากฏทนททกดปม– ‘หลงจากนน ไมมตวอกษร a’ ปรากฏขนเลยเปนเวลาสกครงวนาท
– ‘หลงจากนน ตวอกษร a’ ปรากฏขนหลายตวอยางรวดเรวดวยความถสง
– ‘เมอปลอยปม จะไมมตวอกษร a’ ปรากฏขนอก
คลาส KeySensor
• โคดทใชสรางพฤตกรรมดงกลาวคอนขางซบซอน• เราจะรวมมนเปนคลาสชอวา KeySensor• เราจะใช KeySensor ในการจดการอนพตจากคยบอรดไปตลอด
วธใช KeySensor
• ประกาศ KeySensor ไวเปนฟลดในคลาสทเปนScreen– สมมตประกาศชอวา keySensor
• ในฟงกชน Update ของคลาสทเปน Screen ใหเรยกkeySensor.Update(gameTime)
เพอให keySensor ประมวลผลสถานะของปมตางๆ
วธใช KeySensor
• ตวอยาง: (MenuScreen)
private GameLib.KeySensor keySensor;
public override void Update(GameTime gameTime){
// Update key sensor's internal information. keySensor.Update(gameTime);
: :
}
วธใช KeySensor
• ลงทะเบยนให KeySensor “ ” เฝามอง ปมทเราสนใจ
ดวยคำาสง Watch• โดยมากจะทำาใน constructor ของ Screen
วธใช KeySensor
• ตวอยาง: (MenuScreen)
public MenuScreen(...) : base(game, name){ : : // Create the key sensor. this.keySensor = new KeySensor(); // We will watch three keys: this.keySensor.Watch(Keys.Up); this.keySensor.Watch(Keys.Down); this.keySensor.Watch(Keys.Enter);
: :}
วธใช KeySensor
• ในฟงกชน Update ของ Screen ใหใชฟงกชนเหลานของKeySensor ในการตรวจสถานะของปม– public bool IsKeyPressed(Keys key)
• ตรวจสอบวาคยทใหมาเพงจะถกกดในเฟรมนนหรอไม (เฟรมกอนไมกด)
– public bool IsKeyReleased(Keys key)• ตรวจสอบวาคยทใหมาเพงจะถกปลอยในเฟรมนนหรอไม (เฟรมกอนกดอย)
– public bool IsKeyDown(Keys key)• ตรวจสอบวาคยทใหมาถกกดอยในเฟรมนนหรอไม (เชคกดคาง)
– public bool IsKeyTyped(Keys key)• “ ” ตรวจสอบวาคยทใหมาถก พมพ ในเฟรมนนหรอไม• เมธอดนทำาใหเกดพฤตกรรมเหมอนตอนพมพใน text editor
การใช KeySensor ใน MenuScenepublic override void Update(GameTime gameTime){ // Update key sensor's internal information. keySensor.Update(gameTime);
// If the user types the up arrow, // move the choice upward. if (keySensor.IsKeyTyped(Keys.Up)) { currentChoice -= 1; if (currentChoice < 0) currentChoice = choices.Length - 1; } // If the user types the down arrow, // move the choice downward. else if (keySensor.IsKeyTyped(Keys.Down)) { currentChoice += 1; if (currentChoice >= choices.Length) currentChoice = 0; } // If the user pressed enter, // the choice is selected. else if (keySensor.IsKeyTyped(Keys.Enter)) ChoiceSelected(currentChoice, gameTime);}
การมารกเวลา
จดการเวลา• ในการเขยน KeySensor เราตองสามารถ– มารกเวลาทปมปมหนงถกกดเปนครงแรก– คำานวณวาเวลาผานไปเทาไหรแลวหลงจากปมถกกดครงแรก
• เราตองสามารถใหชอกบมารกเวลาทเราทำาไวดวยเนองจาก– มปมหลายๆ ปมทเราตองตรวจสอบ– แตละปมจะมมารกเหตการณสองแบบ• แบบแรกสำาหรบเวลาทมนถกกดเปนครงแรก• “ ” แบบทสองสำาหรบเวลาทตวอกษรถก พมพ เปนครงสดทาย
คลาส TimeMarker
• public void Mark(string eventName, GameTime now)– มารกเวลาปจจบน (now) ดวยชอทกำาหนด
(eventName) ให• public TimeSpan GetTimeSinceLastMarked(
string eventName, GameTime now)– คนเวลาตงแตเหตการณทมชอทกำาหนดใหถกมารก– คนชวงเวลา 0 ถาไมมเหตการณทกำาหนดให
คลาส TimeMarkerpublic class TimeMarker{ private Dictionary<string, double> markedTimes;
public TimeMarker() { markedTimes = new Dictionary<string, double>(); }
: :}
คลาส TimeMarkerpublic void Mark(string eventName, GameTime now){ markedTimes[eventName] =
now.TotalGameTime.TotalMilliseconds;}
public TimeSpan GetTimeSinceLastMarked(string eventName, GameTime now)
{ if (markedTimes.ContainsKey(eventName)) return TimeSpan.FromMilliseconds( now.TotalGameTime.TotalMilliseconds – markedTimes[eventName]); else return new TimeSpan(365, 0, 0, 0);}
GAME LOGIC
คลาสทเกยวกบเททรส• Tetromino– แทนตวเทโทรมโนหนงตว
• Block– แทนบลอกหนงบลอกจากตวเทโทรมโน
• TetrisBoard– แทนบอรดทใชเลนเททรส
คลาส Block
• แทนบลอกหนงบลอกทมาจากตว Tetromino• ฟลด– x = ตำาแหนงตามแกน X– y = ตำาแหนงตามแกน Y– shape = ชนดของเทโทรมโนตนกำาหนด (I, J, L, O, S, T,
หรอ Z)
TetrominoShape• ชนดขอมลแบบ enum ทเกบชนดของตวเทโทรมโนไวทงหมด
public enum TetrominoShape{ I, J, L, O, S, T, Z}
เขารหสเทโทรมโน• “ ” เทโทรมโนแตละตวม จดหมน ซงเปนบลอกทเวลาหมนเทโทรมโนแลวบลอกอนจะหมนรอบบลอกนน.
• ยกตวอยางเชน เทโทรมโน L จะมบลอกดงเหนขางลางนเปนจดหมน
เขารหสเทโทรมโน• สำาหรบเทโทรมโนแตละตว เราจะสรางระบบพกดของมน
• ใหจดหมนมพกด (0,0).• พกดของบลอกอนๆ คดเทยบตามบลอกนน• ตวอยาง
(0,0)
(0,-1)
(0,1) (1,1)
เขารหสเทโทรมโน• เทโทรมโน L ถกหมนได 4 แบบ ดงนนสามารถแทนได
ดวยบลอกตางๆ ดงจะเหนไดขางลางน {(0,0),(-1,0),(1,0),(-1,1)} {(0,0),(0,-1),(0,1),(1,1)}
{(0,0),(-1,0),(1,0),(1,-1)}
{(0,0),(0,-1),(0,1),(-1,-1)}
เขารหสเทโทรมโน• เพอทำาใหการหมนเทโทรมโนงาย เราจะเกบลสต
ของบลอกทหมนแลว ในลสตซงเรยงตามการหมนทวนเขมนาฬกา
[[(0,0),(-1,0),(1,0),(-1,1)], [(0,0),(0,-1),(0,1),(1,1)], [(0,0),(-1,0),(1,0),(1,-1)] , [(0,0),(0,-1),(0,1),(-1,-1)]]
เขารหสเทโทรมโน• เราทำาเชนนกบเทโทรมโนทกแบบ แลวเกบขอมลไวในดก
ชนนารชอ prototypes ซงเปน static field ของคลาสTetromino
private static Dictionary<TetrominoShape, List<List<Block>>> prototypes = null;
• การสรางดกชนนารนและการเตมมนใหเตม จะถกทำาใน static method ชอ InitializePrototypes
ซงถกเรยกใน constructor ของเกม
คลาส Tetromino
• แทนตวเทโทรมโนหนงตว• มฟลดสฟลด– shape = รปรางของเทโทรมโน ( ชนด TetrominoShape)– rotation = จำานวนเตมทบอกวาตอนนเทโทรมโนอยในการ
หมนทเทาไหร โดยหมายเลขการหมนนอางจากตำาแหนงของการหมนใน
prototypes– x = ตำาแหนงตามแกน X ของจดหมนในบอรด– y = ตำาแหนงตามแกน Y ของจดหมนในบอรด
คลาส Tetromino
• public void RotateClockwise()• public void RotateCounterClockwise()– หมนบลอกตามเขมและทวนเขมนาฬกา– ทำาโดยการเพมหรอลดคา rotation ทละ 1
• public IEnumerable<Block> GetBlocks()– คนบลอกทงหมดในเทโทรมโนมา– บลอกทคนมาจะมตำาแหนง xy อยในระบบพกดของบอรด
คลาส TetrisBoard
• แทนบอรด (กระดาน) ทเราใชเลนเททรส• มฟลดสามฟลด– blocks = ลสตของบลอกในบอรดทตกถงพนแลว (ไมรวมทผเลนบงคบ)
– width = ความกวาง– height = ความสง
การตรวจการชนกนของเทโทรมโนกบบอรด
• public bool CheckSideCollision( Tetromino tetromino)– เชควาเทโทรมโนทใหชนกบขอบดานขางของบอรดหรอไม
– ชน = มบลอกหนงบลอกเลยขอบดานขางไป• public bool CheckBottomCollision(
Tetromino tetromino)– เชควาเทโทรมโนทใหชนกบขอบดานลางบอรดหรอไม– ชน = มบลอกหนงบลอกเลยขอบดานลางไป
การตรวจการชนกนของเทโทรมโนกบบอรด
• public bool CheckBlockCollision( Tetromino tetromino)– เชควาเทโทรมโนทใหชนกบบลอกใดบลอกหนงทอยในบอรดแลวหรอไม
– ชน = มบลอกของเทโทรมโนซอนทบกบบลอกทมอยในบอรดแลวพอด
• public bool CheckCollision( Tetromino tetromino)– เชควาเทโทรมโนทใหชนกบอะไรในบอรดหรอไม– ทำาการเชคสามอยางทแลวทงหมด
เมธอดสำาคญอนๆ• public void Freeze(Tetromino tetromino)– ยอยเทโทรมโนทกำาหนดใหเปนบลอก แลวนำาบลอกไปใสในบอรด
– ใชเวลาเทโทรมโนตกถงพน• public List<int> GetFullRows()– คนลสตของหมายเลขของแถวทเตมแลว
• public void RemoveFullRows()– ลบบลอกในแถวทเตมแลวออกจากบอรด
PLAY SCREEN
Play Screen
Play Screen
• ฟลดทสำาคญ– board = บอรด (instance ของ TetrisBoard)– playerPiece = เทโทรมโนทผใชควบคม– nextPiece = เทโทรมโนอนตอไป (แสดงอยดานขาง)– descendDelay = เวลาจนกวาเทโทรมโนจะเลอนลงขาง
ลางเอง (1 ว)– fullRowsCreated = แถวเตมททำาไดแลว– score = คะแนนททำาได
“ ” โหมด ของ Play Screen
• การควบคมของผใชใน Play Screen แบงออกไดเปน2 โหมดใหญ– Play Mode = ผเลนเลนธรรมดา– Full Row Mode = ผเลนทำาอะไรไมไดเลยระหวางทเกมแสดงวามแถวเตมและมนกำาลงจะหายไป
• ฟลดทชวยจดการสองโหมดน– mode = โหมดปจจบน จะมคาเปน
PlayScreenMode.Play หรอ PlayScreenMode.FullRow– blinkDelay = เวลาในการกระพรบครงรอบของแถวเตม– blinkCounter = แถวเตมกระพรบไปกครงรอบแลว
เมธอดทสำาคญ• public void ResetGame()– เรมเกมใหม– เคลยรบอรด คะแนน ฯลฯ
• private void PrepareNextPiece()– เอา nextPiece ไปใส playerPiece– สรางเทโทรมโนอนใหมแลวเอาไปใส nextPiece
Update
• การเปลยนแปลงสถานภายในของเกมขนอยกบโหมด– Play Mode• รบอนพตจากคยบอรด• ควบคมเทโทรมโน• เชคแถวเตมและเกมโอเวอร
– Full Row Mode• รอใหเวลาผานไปเฉยๆ• ถาเวลาผานไปเกน blinkDelay ใหเพม blinkCounter
Play Mode
• การ update สถานะขนอยกบการเคลอนทของเทโทรมโน– หมน– ไปทางซาย– ไปทางขวา– เลอนลง
หมนเทโทรมโน• เชคปมลกศรขน• ถาหมนแลวไมไปชนอะไรกหมนได
• ใน Updateif (keySensor.IsKeyTyped(Keys.Up)) RotateIfOkay();
• เมธอด RotateIfOkayprivate void RotateIfOkay(){ playerPiece.RotateCounterClockwise(); if (board.CheckCollision(playerPiece)) playerPiece.RotateClockwise();}
เลอนไปทางซายหรอขวา// Move left if the user types the left arrows.if (keySensor.IsKeyTyped(Keys.Left)){ playerPiece.X -= 1; if (board.CheckCollision(playerPiece)) playerPiece.X += 1;}// Move right if the user types the right arrows.else if (keySensor.IsKeyTyped(Keys.Right)){ playerPiece.X += 1; if (board.CheckCollision(playerPiece)) playerPiece.X -= 1;}
เลอนลง• มสองกรณ– “ ”กรณทผใช พมพ ลกศรลง– กรณทเทโทรมโนมนเลอนลงมาเองเมอเวลาผานไป
descendDelay• กรณแรกเชคงาย• กรณทสองตองใช TimeMarker ชวย• ใช TimeMarker จำาเวลาครงทแลวทเทโทรมโนเลอนลง
• ถาเวลาผานจากนนไปเกน descendDelay ใหเลอนลง• เลอนลงใหมารกเวลาเลอนลงใหมทนท
เลอนลงif (keySensor.IsKeyTyped(Keys.Down) || timeMarker.GetTimeSinceLastMarked( LastDescentEventName, gameTime) > descendDelay){ timeMarker.Mark(LastDescentEventName, gameTime); playerPiece.Y += 1; : : }
เลอนลง• เมอเทโทรมโนเลอนลงแลวตองเชค– มนชนกบอะไรหรอไม?– ถาชน• ตอง Freeze มน• เชความแถวเตมหรอไม
– ถาใชใหเปลยนเปน Full Row Mode• เชควา Game Over หรอไม
– ถาใชใหเปลยนเปน Game Over Screen
เลอนลงif (board.CheckCollision(playerPiece)){ playerPiece.Y -= 1; board.Freeze(playerPiece); PrepareNextPiece(); score += PieceScore();
var fullRows = board.GetFullRows(); if (fullRows.Count > 0) { mode = PlayScreenMode.FullRow; ResetFullRowMode(gameTime); }
if (board.CheckCollision(playerPiece)) Game.SwitchScreen("GameOver");}
Full Row Modeif (timeMarker.GetTimeSinceLastMarked( LastBlinkEventName, gameTime) > blinkDelay){ timeMarker.Mark(LastBlinkEventName, gameTime); blinkCounter++;}
if (blinkCounter >= 4){ int fullRowCount = board.GetFullRows().Count; fullRowsCreated += fullRowCount; score += FullRowScore(fullRowCount); board.RemoveFullRows(); mode = PlayScreenMode.Play; timeMarker.Mark(LastDescentEventName, gameTime);}