一步一步写自表达代码——消小球(2)
2012-11-27 14:42
78 查看
本章,我们来讨论如何为游戏加上动作。
先从整体上考虑,很多Java例子程序喜欢直接在代码中加入:
button.addActionListener(new ActionListener() { public void performAction(Event e) {}});
这样的代码。这是不好的,因为这不符合单一职责原则,ActionListener应该独立出去,单独成类。这样,当发生问题的时候也容易寻找。有两种形式,一种是本文采用的单独成类的形式,另外一种是在对应的对象中声明内部类。例如:
class MyButton.ActionListener {}。
整体上来讲,我们会加入:
StartActionListener, ClearActionListener, ExitActionListener和BallActionListener几个类,然后绑定到对象上,下一步对每个动作进行细化。在这期间,我们发现,显示小球的JLabel无法加入ActionListener,于是修改成为JButton——这种情况在编码中很常见,就是无法在详细设计时将设计完美化,只有在编码时才能够发现问题。也是为什么我们主张不需要进行详细设计的原因,具体参照软件开发过程中的浪费——详细设计
关于如何绑定和加入的过程我们省略了。
第一个是Start。当点击Start的时候开始游戏。并且Start按钮变为Restart,表示重新开始当前局。
start之后的动作依次是:
生成随机的小球 Game.getInstance().shuffle();
布局到屏幕上 Game.getInstance().gamePad.main.render();
清空当前分数 Game.getInstance().score.current = 0;
Game.getInstance().score.selected = 0;
刷新分数显示 Game.getInstance().gamePad.helper.render();
更改Start按钮的文字为Restart Game.getInstance().gamePad.control.start.setText("Restart");
据上述代码会产生如下的编译问题:
1.Game中没有包含gamePad对象。
2.很多变量都不是public的无法访问。
3.render方法在MainFrame和HelperFrame中没有声明。
4.shuffle()方法没有声明。
的确如此。自表达代码主张先用可以自表达的方式写出需要的操作,然后再去实现这些操作,而不是先实现一些操作,然后调用。
另外,这些代码可以抽取Game.getInstance()成为一个局部变量。
但是这里,我们发现,Listener试图同时更新Model层和View层,并且加大了代码的访问权限。这并不是一种好的方法。虽然按照这种方法继续下去也可以实现整个代码,但是这中书写方式会把原来拆开的MVC结构又给混合到一起。所以,为了保持分离性,我们不采用上述方式,而是引入消息机制,当数据更新的时候发送消息更新UI。提起这种消息机制,也要提一下不要滥用消息机制,不管什么东西都采用消息传递,因为消息机制虽然可以解耦合代码,但是也降低了可读性和可理解性。
所以,在这里,我们先顶一下,消息机制仅仅用于UI更新。
分布讲解,
startActionListener的代码如下:
其中,game.shuffle()代表生成随机小球。
其代码如下:
但其中涉及到Ball.Color的变更。因为Ball的Color选值范围有限,所以,改为自定义的内部枚举Color
该枚举定义为:
可见,限制了选值范围为红绿蓝黄紫5种颜色,并且每种颜色都配上了一张图片,当重新排列的时候,可以通过名称获取相关的图片并显示。
然后是Score的clearCurrentScore();
再然后是,Game.State,这是一个用来通知按钮变化的中间变量,这个变量也将在游戏结束时起作用。
之后是Event和EventDispatcher
然后开始运行,调试,发现还是有不少Bug的,首先,开始时显示的按钮的边界都不需要,删掉相关代码。
然后,Game初始化时应该初始化其中的变量。
对于Game.gamePad应该在GamePad初始化时进行赋值。
Grid中的变量声明也应该初始化。
然后是各个render的实现
MainFrame.render()
HelperFrame.render()
同时,我们发现旧代码中的selected没有正确赋值,一并修改。
ControlFrame.render()
然后调试,修正错误。
点击Start后的界面如下:
下一章,我们将会讲解BallActionListener的实现。
先从整体上考虑,很多Java例子程序喜欢直接在代码中加入:
button.addActionListener(new ActionListener() { public void performAction(Event e) {}});
这样的代码。这是不好的,因为这不符合单一职责原则,ActionListener应该独立出去,单独成类。这样,当发生问题的时候也容易寻找。有两种形式,一种是本文采用的单独成类的形式,另外一种是在对应的对象中声明内部类。例如:
class MyButton.ActionListener {}。
整体上来讲,我们会加入:
StartActionListener, ClearActionListener, ExitActionListener和BallActionListener几个类,然后绑定到对象上,下一步对每个动作进行细化。在这期间,我们发现,显示小球的JLabel无法加入ActionListener,于是修改成为JButton——这种情况在编码中很常见,就是无法在详细设计时将设计完美化,只有在编码时才能够发现问题。也是为什么我们主张不需要进行详细设计的原因,具体参照软件开发过程中的浪费——详细设计
关于如何绑定和加入的过程我们省略了。
第一个是Start。当点击Start的时候开始游戏。并且Start按钮变为Restart,表示重新开始当前局。
start之后的动作依次是:
生成随机的小球 Game.getInstance().shuffle();
布局到屏幕上 Game.getInstance().gamePad.main.render();
清空当前分数 Game.getInstance().score.current = 0;
Game.getInstance().score.selected = 0;
刷新分数显示 Game.getInstance().gamePad.helper.render();
更改Start按钮的文字为Restart Game.getInstance().gamePad.control.start.setText("Restart");
据上述代码会产生如下的编译问题:
1.Game中没有包含gamePad对象。
2.很多变量都不是public的无法访问。
3.render方法在MainFrame和HelperFrame中没有声明。
4.shuffle()方法没有声明。
的确如此。自表达代码主张先用可以自表达的方式写出需要的操作,然后再去实现这些操作,而不是先实现一些操作,然后调用。
另外,这些代码可以抽取Game.getInstance()成为一个局部变量。
但是这里,我们发现,Listener试图同时更新Model层和View层,并且加大了代码的访问权限。这并不是一种好的方法。虽然按照这种方法继续下去也可以实现整个代码,但是这中书写方式会把原来拆开的MVC结构又给混合到一起。所以,为了保持分离性,我们不采用上述方式,而是引入消息机制,当数据更新的时候发送消息更新UI。提起这种消息机制,也要提一下不要滥用消息机制,不管什么东西都采用消息传递,因为消息机制虽然可以解耦合代码,但是也降低了可读性和可理解性。
所以,在这里,我们先顶一下,消息机制仅仅用于UI更新。
分布讲解,
startActionListener的代码如下:
package org.stephen.bubblebreaker.listener; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import org.stephen.bubblebreaker.control.EventDispatcher; import org.stephen.bubblebreaker.model.Event; import org.stephen.bubblebreaker.model.Game; public class StartActionListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { Game game = Game.getInstance(); game.shuffle(); game.score.clearCurrentScore(); game.state = Game.State.PLAYING; EventDispatcher.send(Event.UPDATE_BALLS); EventDispatcher.send(Event.UPDATE_SCORES); EventDispatcher.send(Event.UPDATE_BUTTONS); } }
其中,game.shuffle()代表生成随机小球。
其代码如下:
public void shuffle() { for (int y = 0; y < 12; y++) { for (int x = 0; x < 12; x++) { grid.balls[y][x] = new Ball(); grid.balls[y][x].color = Ball.Color.values()[(int) (Math .random() * Ball.Color.values().length)]; grid.balls[y][x].x = x; grid.balls[y][x].y = y; grid.balls[y][x].state = Ball.State.NORMAL; } } }
但其中涉及到Ball.Color的变更。因为Ball的Color选值范围有限,所以,改为自定义的内部枚举Color
该枚举定义为:
public enum Color { RED, GREEN, BLUE, YELLOW, PURPLE; public ImageIcon getImageIcon() { return new ImageIcon("image/" + name().toLowerCase() + ".png"); } }
可见,限制了选值范围为红绿蓝黄紫5种颜色,并且每种颜色都配上了一张图片,当重新排列的时候,可以通过名称获取相关的图片并显示。
然后是Score的clearCurrentScore();
public void clearCurrentScore() { selected = 0; current = 0; }
再然后是,Game.State,这是一个用来通知按钮变化的中间变量,这个变量也将在游戏结束时起作用。
public enum State { STAND_BY, PLAYING, GAME_OVER; }
之后是Event和EventDispatcher
package org.stephen.bubblebreaker.model; public class Event { public static final int UPDATE_BALLS = 0x1000; public static final int UPDATE_SCORES = 0x1001; public static final int UPDATE_BUTTONS = 0x1002; }
package org.stephen.bubblebreaker.control; import org.stephen.bubblebreaker.model.Event; import org.stephen.bubblebreaker.model.Game; import org.stephen.bubblebreaker.view.GamePad; public class EventDispatcher { public static void send(int event) { GamePad gamePad = Game.getInstance().gamePad; switch (event) { case Event.UPDATE_BALLS: gamePad.main.render(); break; case Event.UPDATE_SCORES: gamePad.helper.render(); break; case Event.UPDATE_BUTTONS: gamePad.control.render(); break; } } }
然后开始运行,调试,发现还是有不少Bug的,首先,开始时显示的按钮的边界都不需要,删掉相关代码。
然后,Game初始化时应该初始化其中的变量。
private Game() { grid = new Grid(); score = new Score(); state = State.STAND_BY; }
对于Game.gamePad应该在GamePad初始化时进行赋值。
public GamePad() { Game.getInstance().gamePad = this; }
Grid中的变量声明也应该初始化。
package org.stephen.bubblebreaker.model; public class Grid { public Ball[][] balls = new Ball[12][12]; }
然后是各个render的实现
MainFrame.render()
public void render() { Ball[][] balls = Game.getInstance().grid.balls; for (int y = 0; y < 12; y++) { for (int x = 0; x < 12; x++) { this.balls[y][x].setIcon(balls[y][x].color.getImageIcon()); this.balls[y][x].invalidate(); } } }
HelperFrame.render()
public void render() { Score score = Game.getInstance().score; current.setText(String.valueOf(score.current)); selected.setText(String.valueOf(score.selected)); highest.setText(String.valueOf(score.highest)); average.setText(String.valueOf(score.average)); playCount.setText(String.valueOf(score.playCount)); }
同时,我们发现旧代码中的selected没有正确赋值,一并修改。
ControlFrame.render()
public void render() { Game.State state = Game.getInstance().state; if (state == Game.State.PLAYING) { start.setText("Restart"); } else { start.setText("Start"); } }
然后调试,修正错误。
点击Start后的界面如下:
下一章,我们将会讲解BallActionListener的实现。
相关文章推荐
- 一步一步写自表达代码——消小球(1)
- 一步一步写自表达代码——消小球(3)
- 一步一步写自表达代码——消小球(5)
- 一步一步写自表达代码——消小球(6)
- 一步一步写自表达代码——消小球(4)
- 十种表达“你的代码写的很烂”的好方法
- 一步一步jQuery流程设计器插件goflow(附代码) - 3 - 添加流程结点
- 利用正则表达对IP进行排序的实现代码
- 表达:用好代码的肢体语言
- 菜单导航/URHere/面包屑,通过CSS中的content简洁表达代码
- 【Visual C++】游戏开发笔记二十九 一步一步教你用优雅的Direct3D11代码画一个三角形
- 【Visual C++】游戏开发笔记二十九 一步一步教你用优雅的Direct3D11代码画一个三角形
- 立方网页表达形式的代码
- Android实现移动小球和CircularReveal页面切换动画实例代码
- 一步一步教你如何编写VC#,VB.NET或VC++代码玩转Windows Shell Known Folders
- VS2010 自动化整理代码(1)--- VS正则表达替换 PK Vim
- 一步一步教你怎样给Apache Spark贡献代码
- java swing实现小球沿正弦曲线运动的代码
- 《会说话的代码——书写自表达代码之道》书评
- 【AS3代码】滚动的小球