问题
I'm making for learning purpose a space invaders clone. I'm stuck on the issue when an invader got shot. I'm not sure when there is a collision of a bullet with a part of the invader(who is a list of positions) how to erase it and the game to continue. Has anyone got an idea?
Here is the code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SpaceInvader
{
class Program
{
static void Main()
{
Settings.ScreenSettings();
ArmyOfInvaders.InitializeArmyOfInvaders();
Player.InitializePlayer();
int stepCount = 0;
int armyOfInvadersSpeed = 50;
while (true)
{
stepCount++;
if (stepCount % armyOfInvadersSpeed == 0)
{
Draw.EraseItem(ArmyOfInvaders.armyOfInvaders);
Invader.invader.Clear();
ArmyOfInvaders.armyOfInvaders.Clear();
ArmyOfInvaders.InitializeArmyOfInvaders(Movement.moveY, Movement.moveX);
Movement.MovementArmyOfInvaders();
stepCount = 0;
}
Console.CursorVisible = false;
Draw.DrawItem(ArmyOfInvaders.armyOfInvaders);
Draw.EraseItem(Player.player);
Shoot.GenerateShot();
Movement.MovementPlayer();
Draw.DrawItem(Player.player);
Draw.DrawShoot();
Draw.EraseShoot();
//Collision.InvaderGotShot();
Thread.Sleep(Common.gameSpeed);
}
}
}
public class Settings
{
static public int maxRows = 50;
static public int maxCols = 180;
public static void ScreenSettings()
{
Console.CursorVisible = false;
Console.BufferHeight = Console.WindowHeight = maxRows;
Console.BufferWidth = Console.WindowWidth = maxCols;
}
}
public struct Position
{
public int Row { get; set; }
public int Col { get; set; }
public char Symbol { get; set; }
public Position(int row, int col, char symbol)
{
this.Row = row;
this.Col = col;
this.Symbol = symbol;
}
}
public class Player
{
public static List<Position> player = new List<Position>();
public static int playerWide = 9;
public static int playerLong = player.Count;
public static List<Position> InitializePlayer(int row = 0, int col = 0)
{
int startrow = Settings.maxRows - 5 - playerLong;//start position row
int startcol = (Settings.maxCols / 2) - (playerWide / 2);// start position col
player.Add(new Position(startrow + row, startcol + col, 'A'));
player.Add(new Position(startrow + 1 + row, startcol + col, 'o'));
player.Add(new Position(startrow + 2 + row, startcol - 2 + col, '|'));
player.Add(new Position(startrow + 2 + row, startcol + col, 'o'));
player.Add(new Position(startrow + 2 + row, startcol + 2 + col, '|'));
player.Add(new Position(startrow + 3 + row, startcol - 4 + col, '/'));
player.Add(new Position(startrow + 3 + row, startcol - 3 + col, '\\'));
player.Add(new Position(startrow + 3 + row, startcol - 2 + col, '\\'));
player.Add(new Position(startrow + 3 + row, startcol - 1 + col, '\\'));
player.Add(new Position(startrow + 3 + row, startcol + col, 'o'));
player.Add(new Position(startrow + 3 + row, startcol + 1 + col, '/'));
player.Add(new Position(startrow + 3 + row, startcol + 2 + col, '/'));
player.Add(new Position(startrow + 3 + row, startcol + 3 + col, '/'));
player.Add(new Position(startrow + 3 + row, startcol + 4 + col, '\\'));
player.Add(new Position(startrow + 4 + row, startcol - 2 + col, '<'));
player.Add(new Position(startrow + 4 + row, startcol - 1 + col, '/'));
player.Add(new Position(startrow + 4 + row, startcol - 0 + col, 'o'));
player.Add(new Position(startrow + 4 + row, startcol + 1 + col, '\\'));
player.Add(new Position(startrow + 4 + row, startcol + 2 + col, '>'));
return player;
}
}
public class Movement
{
public static bool isRight = true;
public static int moveX = 0;
public static int moveY = 0;
public static int left = -1;
public static int right = 1;
public static int armyOfInvaderJump = 5;
public static void MovementArmyOfInvaders()
{
if (isRight)
{
moveX += armyOfInvaderJump;
if (ArmyOfInvaders.armyOfInvaders[ArmyOfInvaders.armyOfInvaders.Count - 1][Invader.invader.Count - 1].Col >= Settings.maxCols - 20)
{
isRight = false;
moveY += 2;
}
}
else
{
moveX -= armyOfInvaderJump;
if (ArmyOfInvaders.armyOfInvaders[0][0].Col <= 20)
{
isRight = true;
moveY += 2;
}
}
}
public static void MovementPlayer()
{
while (Console.KeyAvailable)
{
ConsoleKeyInfo key = Console.ReadKey(true);
while (Console.KeyAvailable)
{
Console.ReadKey(true);
}
if (Player.player[0].Col - 6 >= 0)
{
if (key.Key == ConsoleKey.LeftArrow)
{
for (int i = 0; i < Player.player.Count; i++)
{
Player.player[i] = new Position(Player.player[i].Row, Player.player[i].Col + left, Player.player[i].Symbol);
}
}
}
if (Player.player[0].Col + 7 < Settings.maxCols)
{
if (key.Key == ConsoleKey.RightArrow)
{
for (int i = 0; i < Player.player.Count; i++)
{
Player.player[i] = new Position(Player.player[i].Row, Player.player[i].Col + right, Player.player[i].Symbol);
}
}
}
if (key.Key == ConsoleKey.Spacebar)
{
Shoot.shoot[Player.player[0].Row - 1, Player.player[0].Col] = 1;
}
}
}
}
class Shoot
{
public static int[,] shoot = new int[Settings.maxRows, Settings.maxCols];
public static void GenerateShot()
{
for (int Row = 0; Row < Shoot.shoot.GetLength(0); Row++)
{
for (int Col = 0; Col < Shoot.shoot.GetLength(1); Col++)
{
if (Shoot.shoot[Row, Col] == 1 && (Row == 0))
{
Shoot.shoot[Row, Col] = 0;
}
if (Shoot.shoot[Row, Col] == 1)
{
Shoot.shoot[Row, Col] = 0;
Shoot.shoot[Row - 1, Col] = 1;
}
}
}
}
}
static public class Invader
{
public static List<Position> invader = new List<Position>();
public static List<Position> InitializeInvader(int row, int col)
{
int startrow = 5;//start position row
int startcol = ArmyOfInvaders.startArmyOfInvadersPosition;// start position col
invader.Add(new Position(startrow + row, startcol + col, '/'));
invader.Add(new Position(startrow + row, startcol + 1 + col, '{'));
invader.Add(new Position(startrow + row, startcol + 2 + col, 'O'));
invader.Add(new Position(startrow + row, startcol + 3 + col, '}'));
invader.Add(new Position(startrow + row, startcol + 4 + col, '\\'));
invader.Add(new Position(startrow + 1 + row, startcol + col, '\\'));
invader.Add(new Position(startrow + 1 + row, startcol + 1 + col, '~'));
invader.Add(new Position(startrow + 1 + row, startcol + 2 + col, '$'));
invader.Add(new Position(startrow + 1 + row, startcol + 3 + col, '~'));
invader.Add(new Position(startrow + 1 + row, startcol + 4 + col, '/'));
return invader;
}
}
public class Draw
{
public static void DrawItem(List<Position> invader)
{
;
foreach (Position part in invader)
{
Console.SetCursorPosition(part.Col, part.Row);
Console.Write((char)part.Symbol);
}
}
public static void DrawItem(List<List<Position>> armyOfInvaders)
{
foreach (List<Position> invader in armyOfInvaders)
{
Draw.DrawItem(invader);
}
}
public static void EraseItem(List<List<Position>> armyOfInvaders)
{
foreach (List<Position> invader in armyOfInvaders)
{
foreach (Position part in invader)
{
Console.SetCursorPosition(part.Col, part.Row);
Console.Write(' ');
}
}
}
public static void EraseItem(List<Position> invader)
{
foreach (Position part in invader)
{
Console.SetCursorPosition(part.Col, part.Row);
Console.Write(' ');
}
}
public static void DrawShoot()
{
for (int Row = 0; Row < Shoot.shoot.GetLength(0); Row++)
{
for (int Col = 0; Col < Shoot.shoot.GetLength(1); Col++)
{
if (Shoot.shoot[Row, Col] == 1)
{
Console.SetCursorPosition(Col, Row);
Console.Write("|");
}
}
}
}
public static void EraseShoot()
{
for (int Row = 0; Row < Shoot.shoot.GetLength(0); Row++)
{
for (int Col = 0; Col < Shoot.shoot.GetLength(1); Col++)
{
if (Row == 0)
{
Console.SetCursorPosition(Col, Row);
Console.Write(" ");
}
if (Shoot.shoot[Row, Col] == 1)
{
Console.SetCursorPosition(Col, Row + 1);
Console.Write(" ");
}
}
}
}
}
public class Collision
{
public static void InvaderGotShot()
{
for (int i = 0; i < ArmyOfInvaders.armyOfInvaders.Count; i++)
{
for (int j = 0; j < Invader.invader.Count; j++)
{
if (Shoot.shoot[ArmyOfInvaders.armyOfInvaders[i][j].Row, ArmyOfInvaders.armyOfInvaders[i][j].Col] == 1)
{
ArmyOfInvaders.armyOfInvaders[i].Clear();
for (int k = 0; k < Invader.invader.Count; k++)
{
ArmyOfInvaders.armyOfInvaders[i].Add(new Position(ArmyOfInvaders.armyOfInvaders[i][k].Row, ArmyOfInvaders.armyOfInvaders[i][k].Col, ' '));
}
}
}
}
}
}
public class ArmyOfInvaders
{
public static int invadersColDistance = 9;
public static int invadersColsCount = 10;
public static int invadersRowDistance = 4;
public static int invadersRowsCount = 4;
public static int startArmyOfInvadersPosition = (Settings.maxCols - (ArmyOfInvaders.invadersColDistance * ArmyOfInvaders.invadersColsCount)) / 2 + 5;//5 e shiranata na edin invader
public static List<List<Position>> armyOfInvaders = new List<List<Position>>();
public static void InitializeArmyOfInvaders(int moveY = 0, int moveX = 0)
{
for (int row = 0; row < invadersRowDistance * invadersRowsCount; row += invadersRowDistance)
{
for (int col = 0; col < invadersColDistance * invadersColsCount; col += invadersColDistance)
{
Invader.InitializeInvader(row + moveY, col + moveX);
}
}
armyOfInvaders.Add(Invader.invader);
}
}
public class Common
{
public static int gameSpeed = 1;
}
}
Thank's in advance!
回答1:
Without claiming to rework your design, I added collection of dead invaders(ArmyOfInvaders.deadInvader
) to stored those ones, who already shot, and changed InvaderGotShot
and DrawItem
methods. I got rid of Invader.invader
collection and introduced local variable in Invader.InitializeInvader
instead of it to prevent misassigning. Please take a look:
class Program
{
static void Main()
{
Settings.ScreenSettings();
ArmyOfInvaders.InitializeArmyOfInvaders();
Player.InitializePlayer();
int stepCount = 0;
int armyOfInvadersSpeed = 50;
while (true)
{
stepCount++;
if (stepCount % armyOfInvadersSpeed == 0)
{
Draw.EraseItem(ArmyOfInvaders.armyOfInvaders);
ArmyOfInvaders.armyOfInvaders.Clear();
ArmyOfInvaders.InitializeArmyOfInvaders(Movement.moveY, Movement.moveX);
Movement.MovementArmyOfInvaders();
stepCount = 0;
}
Console.CursorVisible = false;
Draw.DrawItem(ArmyOfInvaders.armyOfInvaders);
Draw.EraseItem(Player.player);
Shoot.GenerateShot();
Movement.MovementPlayer();
Draw.DrawItem(Player.player);
Draw.DrawShoot();
Draw.EraseShoot();
Collision.InvaderGotShot();
Thread.Sleep(Common.gameSpeed);
}
}
}
public class Settings
{
static public int maxRows = 50;
static public int maxCols = 180;
public static void ScreenSettings()
{
Console.CursorVisible = false;
Console.BufferHeight = Console.WindowHeight = maxRows;
Console.BufferWidth = Console.WindowWidth = maxCols;
}
}
public struct Position
{
public int Row { get; set; }
public int Col { get; set; }
public char Symbol { get; set; }
public Position(int row, int col, char symbol)
{
this.Row = row;
this.Col = col;
this.Symbol = symbol;
}
}
public class Player
{
public static List<Position> player = new List<Position>();
public static int playerWide = 9;
public static int playerLong = player.Count;
public static List<Position> InitializePlayer(int row = 0, int col = 0)
{
int startrow = Settings.maxRows - 5 - playerLong;//start position row
int startcol = (Settings.maxCols / 2) - (playerWide / 2);// start position col
player.Add(new Position(startrow + row, startcol + col, 'A'));
player.Add(new Position(startrow + 1 + row, startcol + col, 'o'));
player.Add(new Position(startrow + 2 + row, startcol - 2 + col, '|'));
player.Add(new Position(startrow + 2 + row, startcol + col, 'o'));
player.Add(new Position(startrow + 2 + row, startcol + 2 + col, '|'));
player.Add(new Position(startrow + 3 + row, startcol - 4 + col, '/'));
player.Add(new Position(startrow + 3 + row, startcol - 3 + col, '\\'));
player.Add(new Position(startrow + 3 + row, startcol - 2 + col, '\\'));
player.Add(new Position(startrow + 3 + row, startcol - 1 + col, '\\'));
player.Add(new Position(startrow + 3 + row, startcol + col, 'o'));
player.Add(new Position(startrow + 3 + row, startcol + 1 + col, '/'));
player.Add(new Position(startrow + 3 + row, startcol + 2 + col, '/'));
player.Add(new Position(startrow + 3 + row, startcol + 3 + col, '/'));
player.Add(new Position(startrow + 3 + row, startcol + 4 + col, '\\'));
player.Add(new Position(startrow + 4 + row, startcol - 2 + col, '<'));
player.Add(new Position(startrow + 4 + row, startcol - 1 + col, '/'));
player.Add(new Position(startrow + 4 + row, startcol - 0 + col, 'o'));
player.Add(new Position(startrow + 4 + row, startcol + 1 + col, '\\'));
player.Add(new Position(startrow + 4 + row, startcol + 2 + col, '>'));
return player;
}
}
public class Movement
{
public static bool isRight = true;
public static int moveX = 0;
public static int moveY = 0;
public static int left = -1;
public static int right = 1;
public static int armyOfInvaderJump = 5;
public static void MovementArmyOfInvaders()
{
if (isRight)
{
moveX += armyOfInvaderJump;
if (ArmyOfInvaders.armyOfInvaders[ArmyOfInvaders.armyOfInvaders.Count - 1][9].Col >= Settings.maxCols - 20)
{
isRight = false;
moveY += 2;
}
}
else
{
moveX -= armyOfInvaderJump;
if (ArmyOfInvaders.armyOfInvaders[0][0].Col <= 20)
{
isRight = true;
moveY += 2;
}
}
}
public static void MovementPlayer()
{
while (Console.KeyAvailable)
{
ConsoleKeyInfo key = Console.ReadKey(true);
while (Console.KeyAvailable)
{
Console.ReadKey(true);
}
if (Player.player[0].Col - 6 >= 0)
{
if (key.Key == ConsoleKey.LeftArrow)
{
for (int i = 0; i < Player.player.Count; i++)
{
Player.player[i] = new Position(Player.player[i].Row, Player.player[i].Col + left, Player.player[i].Symbol);
}
}
}
if (Player.player[0].Col + 7 < Settings.maxCols)
{
if (key.Key == ConsoleKey.RightArrow)
{
for (int i = 0; i < Player.player.Count; i++)
{
Player.player[i] = new Position(Player.player[i].Row, Player.player[i].Col + right, Player.player[i].Symbol);
}
}
}
if (key.Key == ConsoleKey.Spacebar)
{
Shoot.shoot[Player.player[0].Row - 1, Player.player[0].Col] = 1;
}
}
}
}
class Shoot
{
public static int[,] shoot = new int[Settings.maxRows, Settings.maxCols];
public static void GenerateShot()
{
for (int Row = 0; Row < Shoot.shoot.GetLength(0); Row++)
{
for (int Col = 0; Col < Shoot.shoot.GetLength(1); Col++)
{
if (Shoot.shoot[Row, Col] == 1 && (Row == 0))
{
Shoot.shoot[Row, Col] = 0;
}
if (Shoot.shoot[Row, Col] == 1)
{
Shoot.shoot[Row, Col] = 0;
Shoot.shoot[Row - 1, Col] = 1;
}
}
}
}
}
static public class Invader
{
public static List<Position> InitializeInvader(int row, int col)
{
int startrow = 5;//start position row
int startcol = ArmyOfInvaders.startArmyOfInvadersPosition;// start position col
List<Position> invader = new List<Position>();
invader.Add(new Position(startrow + row, startcol + col, '/'));
invader.Add(new Position(startrow + row, startcol + 1 + col, '{'));
invader.Add(new Position(startrow + row, startcol + 2 + col, 'O'));
invader.Add(new Position(startrow + row, startcol + 3 + col, '}'));
invader.Add(new Position(startrow + row, startcol + 4 + col, '\\'));
invader.Add(new Position(startrow + 1 + row, startcol + col, '\\'));
invader.Add(new Position(startrow + 1 + row, startcol + 1 + col, '~'));
invader.Add(new Position(startrow + 1 + row, startcol + 2 + col, '$'));
invader.Add(new Position(startrow + 1 + row, startcol + 3 + col, '~'));
invader.Add(new Position(startrow + 1 + row, startcol + 4 + col, '/'));
return invader;
}
}
public class Draw
{
public static void DrawItem(List<Position> invader)
{
;
foreach (Position part in invader)
{
Console.SetCursorPosition(part.Col, part.Row);
Console.Write((char)part.Symbol);
}
}
public static void DrawItem(List<List<Position>> armyOfInvaders)
{
for (var i = 0; i < armyOfInvaders.Count; i++)
{
if (!ArmyOfInvaders.deadInvaders.Contains(i))
{
Draw.DrawItem(armyOfInvaders[i]);
}
}
}
public static void EraseItem(List<List<Position>> armyOfInvaders)
{
foreach (List<Position> invader in armyOfInvaders)
{
foreach (Position part in invader)
{
Console.SetCursorPosition(part.Col, part.Row);
Console.Write(' ');
}
}
}
public static void EraseItem(List<Position> invader)
{
foreach (Position part in invader)
{
Console.SetCursorPosition(part.Col, part.Row);
Console.Write(' ');
}
}
public static void DrawShoot()
{
for (int Row = 0; Row < Shoot.shoot.GetLength(0); Row++)
{
for (int Col = 0; Col < Shoot.shoot.GetLength(1); Col++)
{
if (Shoot.shoot[Row, Col] == 1)
{
Console.SetCursorPosition(Col, Row);
Console.Write("|");
}
}
}
}
public static void EraseShoot()
{
for (int Row = 0; Row < Shoot.shoot.GetLength(0); Row++)
{
for (int Col = 0; Col < Shoot.shoot.GetLength(1); Col++)
{
if (Row == 0)
{
Console.SetCursorPosition(Col, Row);
Console.Write(" ");
}
if (Shoot.shoot[Row, Col] == 1)
{
Console.SetCursorPosition(Col, Row + 1);
Console.Write(" ");
}
}
}
}
}
public class Collision
{
public static void InvaderGotShot()
{
for (int i = 0; i < ArmyOfInvaders.armyOfInvaders.Count; i++)
{
for (int j = 0; j < ArmyOfInvaders.armyOfInvaders[i].Count; j++)
{
if (Shoot.shoot[ArmyOfInvaders.armyOfInvaders[i][j].Row, ArmyOfInvaders.armyOfInvaders[i][j].Col] == 1)
{
if (!ArmyOfInvaders.deadInvaders.Contains(i))
{
Shoot.shoot[ArmyOfInvaders.armyOfInvaders[i][j].Row,
ArmyOfInvaders.armyOfInvaders[i][j].Col] = 0;
ArmyOfInvaders.deadInvaders.Add(i);
return;
}
}
}
}
}
}
public class ArmyOfInvaders
{
public static int invadersColDistance = 9;
public static int invadersColsCount = 10;
public static int invadersRowDistance = 4;
public static int invadersRowsCount = 4;
public static List<int> deadInvaders = new List<int>();
public static int startArmyOfInvadersPosition = (Settings.maxCols - (ArmyOfInvaders.invadersColDistance * ArmyOfInvaders.invadersColsCount)) / 2 + 5;//5 e shiranata na edin invader
public static List<List<Position>> armyOfInvaders = new List<List<Position>>();
public static void InitializeArmyOfInvaders(int moveY = 0, int moveX = 0)
{
for (int row = 0; row < invadersRowDistance * invadersRowsCount; row += invadersRowDistance)
{
for (int col = 0; col < invadersColDistance * invadersColsCount; col += invadersColDistance)
{
var invader = Invader.InitializeInvader(row + moveY, col + moveX);
armyOfInvaders.Add(invader);
}
}
}
}
public class Common
{
public static int gameSpeed = 1;
}
The point was to hide dead aliens from user, but not to exclude them from collection. Your implementatiion tried to clear positions of shot alien, but on the next iteration it initialized all invaders again.
Also notice that I was forced to introduce literal 9
in Movement.MovementArmyOfInvaders
instead of call Invader.invader.Length
.
Hope it helps.
回答2:
The additiional answer for question about proper way for storing invaders. I am not sure know if it is appropriate to create another answer, but the code snippet turns out rather big. Here is an example of design. It is just a prototype for understanding the idea. I did not try to come up with complete solution, you are free to modify it in any way. I leaved most of the implementation details for you and tried to provide full explanation in comments. The main idea is to avoid static state and replace it with encapsulated state within object:
/// <summary>
/// Invaders, that can move and be shot
/// </summary>
public class ArmyOfInvaders
{
/// <summary>
/// Alive invaders
/// </summary>
private readonly List<Invader> _invaders = new List<Invader>();
/// <summary>
/// Initializes all invaders once on start of the game
/// Provide them initial coordinates and add them to <see cref="_invaders"/>
/// </summary>
public void Initialize()
{
}
/// <summary>
/// Moves all invaders somewhere
/// Erases invaders, moves them and draw again
/// </summary>
public void Move()
{
int xMove = 0;
int yMove = 0;
// Here some logic around moving of entire cloud of invaders
// computing of xMove and yMove
foreach (var invader in _invaders)
{
invader.Erase();
invader.Move(xMove, yMove);
invader.Draw();
}
}
/// <summary>
/// Handles shoot to invaders
/// </summary>
/// <param name="shootX">X coordinate of the bullet</param>
/// <param name="shootY">Y coordinate of the bullet</param>
public void HandleShoot(int shootX, int shootY)
{
var victim = _invaders.FirstOrDefault(i => i.IsShot(shootX, shootY));
if (victim != null)
{
_invaders.Remove(victim);
}
}
}
/// <summary>
/// Single invader
/// </summary>
/// <remarks>
/// Invader really needs only one postion(x,y), let say top-left point
/// All symbols, which it consists of, can be printed relatively of this point.
/// </remarks>
public class Invader
{
/// <summary>
/// X coordinate of invader
/// </summary>
private int _x;
/// <summary>
/// Y coordinate of invader
/// </summary>
private int _y;
/// <summary>
/// Ctor
/// </summary>
/// <param name="x">Initial x coordinate</param>
/// <param name="y">Initial y coordinate</param>
public Invader(int x, int y)
{
Move(x, y);
}
/// <summary>
/// Draws the invader by current coordinates
/// Invader really needs only one postion(x,y), let say top-left point
/// All symbols, which it consists of, can be printed relatively of this point.
/// </summary>
public void Draw()
{
// here printing of symbols
}
/// <summary>
/// Changes the coordinates of invader
/// </summary>
public void Move(int xMove, int yMove)
{
_x += xMove;
_y += yMove;
}
/// <summary>
/// Erases the invader by current coordinates
/// </summary>
public void Erase()
{
// here erasing of symbols
}
/// <summary>
/// Returns true if some part of invaders is crushed by bullet
/// </summary>
/// <param name="shootX">X coordinate of bullet</param>
/// <param name="shootY">Y coordinate of bullet</param>
public bool IsShot(int shootX, int shootY)
{
return // here logic of crushing
}
}
Hope it helps.
来源:https://stackoverflow.com/questions/52138460/shooting-collision-of-the-shot-with-a-collection-of-list