我最近了解了设计模式,并想更改我必须制作的一款小游戏的代码。该游戏称为SpaceTaxi。我做了一个解析器,解析一个带有ascii内容的.txt文件,生成了4种不同的实体列表:滑行,障碍物,出口和平台。在游戏循环内部,我使用这些实体检测它们是否发生碰撞来调用一种大碰撞方法。这真是丑陋的代码。有没有可以用于碰撞方法的好的设计模式?因此,与其使用大型方法,不如使用较小的类?当前看起来像这样:
/// <summary>
/// Checks for collision with obstacles, platforms and exits.
/// </summary>
private void CollisonCheck() {
var platformCollision = Player.CollisionPlatform(Parser.PlatformEntities,
false);
var obstacleCollision = Player.CollisionObstacle(Parser.ObstacleEntities,
false);
var exitCollision = Player.CollisionObstacle(Parser.ExitEntities,
false);
// Landing on platform
if (platformCollision.Item1 && obstacleCollision) {
// Stand still on platform
if (Math.Abs(Player.Entity.Shape.AsDynamicShape().Direction.Y)
< Constants.COLLISION_DISTANCE) {
Player.Shape.Direction.Y = 0;
Player.Shape.Direction.X = 0;
Player.OnPlatform = true;
// Explode because of too much speed
} else {
AddExplosion(Player.Shape.Position.X, Player.Shape.Position.Y,
0.1f, 0.1f);
}
// Be rewarded in case player transports a customer
if (Player.HasCostumer) {
foreach (var customer in pickedUpCustomers) {
if (CorrectDestination(platformCollision.Item2,
customer.DestinationPlatform)) {
score.AddPoint(CurrentCustomer.RewardPoints);
customer.CanRemove = true;
Player.HasCostumer = false;
}
}
}
// Exit map
} else if (exitCollision) {
// Switch from one map to another
if (GameRunning.CurrentMap == "the-beach.txt") {
GameRunning.CurrentMap = "short-n-sweet.txt";
Player.SetPosition(Constants.PLAYER_ENTRYPOSITION_X,
Constants.PLAYER_ENTRYPOSITION_Y);
Player.Entity.Shape.AsDynamicShape().Direction.Y = Constants.STILL;
Player.Entity.Shape.AsDynamicShape().Direction.X = Constants.STILL;
// Switch from one map to another
} else {
GameRunning.CurrentMap = "the-beach.txt";
Player.SetPosition(Constants.PLAYER_ENTRYPOSITION_X,
Constants.PLAYER_ENTRYPOSITION_Y);
Player.Entity.Shape.AsDynamicShape().Direction.Y = Constants.STILL;
Player.Entity.Shape.AsDynamicShape().Direction.X = Constants.STILL;
}
GameRunning.Timer.Restart();
Parser.Load(GameRunning.CurrentMap);
allCustomersInMap = new List<Customer>();
foreach (var c in Parser.Customer) {
allCustomersInMap.Add(new Customer(c.Key, c.Value.Item1,
c.Value.Item2, c.Value.Item3, c.Value.Item4,
c.Value.Item5));
}
// Collision with obstacle. Add explosion
} else if (obstacleCollision) {
AddExplosion(Player.Shape.Position.X, Player.Shape.Position.Y,
Constants.EXPLOSION_WIDTH, Constants.EXPLOSION_HEIGHT);
TaxiBus.GetBus()
.RegisterEvent(GameEventFactory<object>.CreateGameEventForAllProcessors(
GameEventType.GameStateEvent, this, "CHANGE_STATE",
"MAIN_MENU", ""));
}
// Collision with taxi and customer
// CollisionCustomer returns a bool (item1) and null/customer (item2)
if (Player.CollisionCustomer(allCustomersInMap).Item1 && !Player.HasCostumer) {
var customer = Player.CollisionCustomer(allCustomersInMap).Item2;
TaxiMeterTimer = new Stopwatch();
TaxiMeterTimer.Start();
CurrentCustomer = customer;
pickedUpCustomers.Add(customer);
allCustomersInMap.Remove(customer);
CurrentCustomer.SetPosition(Constants.HIDEPOS_X, Constants.HIDEPOS_Y);
Player.HasCostumer = true;
}
}
答案 0 :(得分:0)
请注意,类似这样的问题更适合CodeReview,因为对于SO来说,这几乎是不重要的,因为您没有要解决的特定问题或例外。
出于各种原因,在所有应用程序中将决策逻辑与动作逻辑分开是一种很好的做法:
只需将您定义的动作分离出来,结果将类似于以下代码:
/// <summary>
/// Checks for collision with obstacles, platforms and exits.
/// </summary>
private void CollisonCheck()
{
var platformCollision = Player.CollisionPlatform(Parser.PlatformEntities, false);
var obstacleCollision = Player.CollisionObstacle(Parser.ObstacleEntities, false);
var exitCollision = Player.CollisionObstacle(Parser.ExitEntities, false);
// Landing on platform
if (platformCollision.Item1 && obstacleCollision)
DockAtPlatform(); // Stand still on platform
else if (exitCollision)
ExitMap(); // Exit map
else if (obstacleCollision)
CollideWithObject(); // Collision with obstacle. Add explosion
// ??? Player collision can occur at a platform or in general regions in the map
if (Player.CollisionCustomer(allCustomersInMap).Item1 && !Player.HasCostumer)
PickupCustomer();
}
/// <summary>
/// Dock Player with platform as long as they are not approaching too fast.
/// Collect reward if carrying a passenger
/// </summary>
/// <remarks>If too fast, player will explode!</remarks>
private void DockAtPlatform()
{
if (Math.Abs(Player.Entity.Shape.AsDynamicShape().Direction.Y)
< Constants.COLLISION_DISTANCE)
{
Player.Shape.Direction.Y = 0;
Player.Shape.Direction.X = 0;
Player.OnPlatform = true;
// Explode because of too much speed
}
else
{
AddExplosion(Player.Shape.Position.X, Player.Shape.Position.Y,
0.1f, 0.1f);
}
// Be rewarded in case player transports a customer
if (Player.HasCostumer)
{
foreach (var customer in pickedUpCustomers)
{
if (CorrectDestination(platformCollision.Item2,
customer.DestinationPlatform))
{
score.AddPoint(CurrentCustomer.RewardPoints);
customer.CanRemove = true;
Player.HasCostumer = false;
}
}
}
}
/// <summary>
/// Switch between Maps
/// </summary>
private void ExitMap()
{
// Switch from one map to another
if (GameRunning.CurrentMap == "the-beach.txt")
{
GameRunning.CurrentMap = "short-n-sweet.txt";
Player.SetPosition(Constants.PLAYER_ENTRYPOSITION_X,
Constants.PLAYER_ENTRYPOSITION_Y);
Player.Entity.Shape.AsDynamicShape().Direction.Y = Constants.STILL;
Player.Entity.Shape.AsDynamicShape().Direction.X = Constants.STILL;
}
else
{
// Switch the reverse way around
GameRunning.CurrentMap = "the-beach.txt";
Player.SetPosition(Constants.PLAYER_ENTRYPOSITION_X,
Constants.PLAYER_ENTRYPOSITION_Y);
Player.Entity.Shape.AsDynamicShape().Direction.Y = Constants.STILL;
Player.Entity.Shape.AsDynamicShape().Direction.X = Constants.STILL;
}
GameRunning.Timer.Restart();
Parser.Load(GameRunning.CurrentMap);
allCustomersInMap = new List<Customer>();
foreach (var c in Parser.Customer)
{
allCustomersInMap.Add(new Customer(c.Key, c.Value.Item1,
c.Value.Item2, c.Value.Item3, c.Value.Item4,
c.Value.Item5));
}
}
/// <summary>
/// Show explosion because player has collided with an object, then return to the main menu
/// </summary>
private void CollideWithObject()
{
AddExplosion(Player.Shape.Position.X, Player.Shape.Position.Y,
Constants.EXPLOSION_WIDTH, Constants.EXPLOSION_HEIGHT);
TaxiBus.GetBus()
.RegisterEvent(GameEventFactory<object>.CreateGameEventForAllProcessors(
GameEventType.GameStateEvent, this, "CHANGE_STATE",
"MAIN_MENU", ""));
}
/// <summary>
/// Pickup a new customer, start the meter running and remove the customer from the map
/// </summary>
private void PickupCustomer()
{
var customer = Player.CollisionCustomer(allCustomersInMap).Item2;
TaxiMeterTimer = new Stopwatch();
TaxiMeterTimer.Start();
CurrentCustomer = customer;
pickedUpCustomers.Add(customer);
allCustomersInMap.Remove(customer);
CurrentCustomer.SetPosition(Constants.HIDEPOS_X, Constants.HIDEPOS_Y);
Player.HasCostumer = true;
}
现在,您可以专注于单个操作并对其进行检查,以查看是否存在更干净的方法来对其过程进行编码。
“添加爆炸”应该接受一个点对象,而不是通过X,Y坐标传递,这使代码更易于阅读,并且是传递始终在一起的坐标的自然方法。
您应该创建特定的数据模型类以用作冲突函数的返回值,而不是使用Tuples,这听起来像是过度设计,但它有助于代码的文档编制和理解意图。
.Item1
和.Item2
隐含在数据类型和逻辑含义内if
块的注释放在上一个分支代码的内部,将注释移到受影响的if分支内部,或紧接在其上方
最终注释(个人)
尝试将方法逻辑更改为更多因果样式,如果在OO逻辑中采用更多的函数式编程样式,则可以在整体 SDLC 中获得很多好处。您可以在哪里,尤其是对于最小的工作单元,传入将作为方法的参数操作的对象,而不是引用全局对象,这样,您将发现建立单元测试和代码审查更容易个别方法。即使在这个项目中您可能没有实施单元测试,但这也是一个很好的习惯,它会使您以后发布在SO上的代码示例更容易被我们其他人调试,并使您成为团队或社区发展项目中更高效的贡献者。