Shimeji开源桌宠代码学习(1)

来源:互联网 发布:淘宝him折扣店是真的吗 编辑:程序博客网 时间:2024/05/16 08:39
Shimeji在日语中本意为“蘑菇”。

我们这里的Shimeji是种可以在电脑桌面上四处走动,玩耍,分裂以及卖萌捣乱的桌面程序。

这种桌面程序具有高度可配置的特点。其运行方式是依靠xml文件来控制吉祥物的动作及动作频度。而吉祥物的形象和特殊动作可以通过替换图片来达到定制的效果。

Shimeji 程序由日本的Yuki Yamada开发制作,其官方网页为:

www.group-finity.com/Shimeji/

现在我们所见到的各式各样不同的shimeji形象,也均是由各个作者利用官方网站提供的原始程序修改而成的。

由于现在该网站似乎已经停止维护,所以这里使用的是一个Github上的作者开发的Shimeji4mac的项目作为本博客的学习内容

地址为:

https://github.com/nonowarn/shimeji4mac

我的fork地址为

https://github.com/AlanJager/shimeji4mac

原本考虑到作者太久没有上Github所以不好进行pull request,抱着试一试的心态给作者写了一封邮件,没想到收到了回复


所以有意向进行开发的朋友也可以放心了。接下来就进入正题了,

首先打开Main.java

private static final Logger log = Logger.getLogger(Main.class.getName());static final String BEHAVIOR_GATHER = "マウスの周りに集まる";static {try {LogManager.getLogManager().readConfiguration(Main.class.getResourceAsStream("/logging.properties"));} catch (final SecurityException e) {e.printStackTrace();} catch (final IOExceptn e) {e.printStackTrace();}}private static Main instance = new Main();public static Main getInstance() {return instance;}private final Manager manager = new Manager();private final Configuration configuration = new Configuration();

首先是util.logging.Logger类,大多我们都使用log4j进行日志记录,对这个难免陌生,下面一篇介绍的比较详细的文章

java.util.logging.Logger使用详解

我也简短的说明一下,Logger类最大的特点就是对日志的级别分的非常详细,所有级别都定义在util.logging.Level类中,

分别是:Severe,Warning,Info,Config,Fine,Finer,Finest,

此外还有OFF级别用于关闭日志,All则是启动所有日志记录

基本的使用方式如下:

log.log(Level.INFO, "設定ファイルを読み込み({0})", "/動作.xml");


然后声明了一个静态常量,

BEHAVIOR_GATHER = "マウスの周りに集まる";

这个变量用于响应聚集事件时,创建所有shimeji的动作。

之后的LogManager用于管理日志,详细可以参考

java.util.logging.LogManager

对基本使用进行了说明。


然后创建了静态的Main类和获取Main类的方法,这样做的原因非常明显,这也是何时使用static的习惯

这里翻译一段Stack Overflow上面一个回答:有一个这样的法则,问自己不构建Obj而去直接调用方法是否有意义,

如果有意义那么就用使它为static。比方说你的汽车类Car里有一个方法Car::convertMPpgToKpl(double mpg),可能

有人需要使用转换的方法,但是并不需要构建一个Car实例。

这大致就是个人认同的使用static的原则。

然后继续创建了Manger和Configuration的实例。


紧接着就是主函数的执行

public static void main(final String[] args) {getInstance().run();}public void run() {// 設定を読み込むloadConfiguration();// トレイアイコンを作成するcreateTrayIcon();// しめじを一匹作成するcreateMascot();getManager().start();}

通过之前的static method获取main实例,然后调用定义的方法Main::run()

首先是调用Main::loadConfiguration()

        private void loadConfiguration() {try {log.log(Level.INFO, "設定ファイルを読み込み({0})", "/動作.xml");final Document actions = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(Main.class.getResourceAsStream("/動作.xml"));log.log(Level.INFO, "設定ファイルを読み込み({0})", "/行動.xml");this.getConfiguration().load(new Entry(actions.getDocumentElement()));final Document behaviors = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(Main.class.getResourceAsStream("/行動.xml"));this.getConfiguration().load(new Entry(behaviors.getDocumentElement()));this.getConfiguration().validate();} catch (final IOException e) {log.log(Level.SEVERE, "設定ファイルの読み込みに失敗", e);exit();} catch (final SAXException e) {log.log(Level.SEVERE, "設定ファイルの読み込みに失敗", e);exit();} catch (final ParserConfigurationException e) {log.log(Level.SEVERE, "設定ファイルの読み込みに失敗", e);exit();} catch (final ConfigurationException e) {log.log(Level.SEVERE, "設定ファイルの記述に誤りがあります", e);exit();}}

首先使用DocumentBuilderFactory来parse一个xml文件,如官方文档描述,DocumentBuilderFactory:

Defines a factory API that enables applications to obtain a parser that produces DOM object trees from XML documents.

这里使用的是w3c的DOM来解析xml文件,关于如何用 DocumentBuilderFactory解析xml这篇文章里以Shimeji的部分代码为例子进行了说明。当然除了使用DOM之外,Java还有许多用于解析xml文件的方法, java中四种操作(DOM、SAX、JDOM、DOM4J)xml方式详解与比较中对这几种方法进行了详细说明,并且给出比较其效率的代码。由于这个桌宠的xml文件本身并不是很大,所以使用DOM处理绰绰有余。

接下来我们来看Configuration::load()这个方法,类似DocumentBuilderFactory解析xml,Shimeji中定义了Entry类作为几本节点,我们先来看Entry类的定义:

public class Entry {private Element element;private Map<String, String> attributes;private List<Entry> children;private Map<String, List<Entry> > selected = new HashMap<String, List<Entry>>();public Entry(final Element element){this.element = element;}public String getName() {return this.element.getTagName();}}

定义了基本的属性和get方法,这里的Element实际上是Dom元素中的一个接口,它的super interface就是node,然后对属性,子节点进行了定义。然后需要给这个类添加方法,根据解析xml管用思路可以知道,我们需要获取属性和获取节点的方法这里就不多赘述了。

我们看下面实现的一个方法:

public Map<String, String> getAttributes() {if ( this.attributes!=null) {return this.attributes;}this.attributes = new LinkedHashMap<String, String>();final NamedNodeMap attrs = this.element.getAttributes();for(int i = 0; i<attrs.getLength(); ++i ) {final Attr attr = (Attr)attrs.item(i);this.attributes.put(attr.getName(), attr.getValue());}return this.attributes;}
作者使用了LinkedHashMap用于储存xml中节点的多个属性,通过 LinkedHashMap, HashMap以及TreeHashMap的比较实际上使用LinkedHashMap的好处是能够保证放入元素的顺序,我们先记录这个特点,在这里似乎还没体现出使用LinkedHashMap的优势。

在Configuration::load()方法里,对每一个xml文件中定义的动作进行了创建,

for (final Entry node : list.selectChildren("動作")) {final ActionBuilder action = new ActionBuilder(this, node);if ( this.getActionBuilders().containsKey(action.getName())) {throw new ConfigurationException("動作の名前が重複しています:"+action.getName());}System.out.println("action name is: " + action.getName());this.getActionBuilders().put(action.getName(), action);}
同时附上一个动作的xml内容:

<動作 名前="歩く" 種類="移動" 枠="地面"><アニメーション><ポーズ 画像="/shime1.png" 基準座標="64,128" 移動速度="-2,0" 長さ="6" /><ポーズ 画像="/shime2.png" 基準座標="64,128" 移動速度="-2,0" 長さ="6" /><ポーズ 画像="/shime1.png" 基準座標="64,128" 移動速度="-2,0" 長さ="6" /><ポーズ 画像="/shime3.png" 基準座標="64,128" 移動速度="-2,0" 長さ="6" /></アニメーション></動作>
传入一个动作节点,然后交给ActionBuilder处理,ActionBuilder的constructor()如下:

public ActionBuilder(final Configuration configuration, final Entry actionNode) throws IOException {this.name = actionNode.getAttribute("名前");this.type = actionNode.getAttribute("種類");this.className = actionNode.getAttribute("クラス");log.log(Level.INFO, "動作読み込み開始({0})", this);this.getParams().putAll(actionNode.getAttributes());for (final Entry node : actionNode.selectChildren("アニメーション")) {this.getAnimationBuilders().add(new AnimationBuilder(node));}for (final Entry node : actionNode.getChildren()) {if (node.getName().equals("動作参照")) {this.getActionRefs().add(new ActionRef(configuration, node));} else if (node.getName().equals("動作")) {this.getActionRefs().add(new ActionBuilder(configuration, node));}}log.log(Level.INFO, "動作読み込み完了");}
通过读取一个动作下的アニメーション,并将其传递给AnimationBuilder,

接下来我们看AnimationBuilder的constructor()

public AnimationBuilder(final Entry animationNode) throws IOException {this.condition = animationNode.getAttribute("条件") == null ? "true" : animationNode.getAttribute("条件");log.log(Level.INFO, "アニメーション読み込み開始");for (final Entry frameNode : animationNode.getChildren()) {this.getPoses().add(loadPose(frameNode));}log.log(Level.INFO, "アニメーション読み込み完了");}
对于读入的一个节点判断其是否满足发生条件,如果满足则对Pose进行读取,每一个Pose对应一张png图片

private Pose loadPose(final Entry frameNode) throws IOException {final String imageText = frameNode.getAttribute("画像");final String anchorText = frameNode.getAttribute("基準座標");final String moveText = frameNode.getAttribute("移動速度");final String durationText = frameNode.getAttribute("長さ");final String[] anchorCoordinates = anchorText.split(",");final Point anchor = new Point(Integer.parseInt(anchorCoordinates[0]), Integer.parseInt(anchorCoordinates[1]));final ImagePair image = ImagePairLoader.load(imageText, anchor);final String[] moveCoordinates = moveText.split(",");final Point move = new Point(Integer.parseInt(moveCoordinates[0]), Integer.parseInt(moveCoordinates[1]));final int duration = Integer.parseInt(durationText);final Pose pose = new Pose(image, move.x, move.y, duration);log.log(Level.INFO, "姿勢読み込み({0})", pose);return pose;}

同时对图像的锚点,持续时间,移动长度进行了设置,最后返回一个Pose实例。整个流程结束后,一整个アニメーション将会存储在poses这个ArrayList内,同时ActionBuilder里则使用一个ArrayList来存储一系列这样的AnimationBuilder。即通过ActionBuilder管理所有的AnimationBuilder,然后每一个AnimationBuilder管理一组Poses。

继续看ActionBuilder的constructor,这里还使用了ActionRef这个类,这里主要处理的是复合动作的实现,此处仅仅将该对应的复合动作节点放在了ActionRef的ArrayList内,到此ActionBuilder则完成了工作。简单动作和复合动作分别对应了一个ActionBuilder。

然后Configuration::load()进行了Behavior的加载

for (final Entry list : configurationNode.selectChildren("行動リスト")) {log.log(Level.INFO, "行動リスト...");loadBehaviors(list, new ArrayList<String>());}

类似对动作的读取,通过定义BehaviorBuilder来描述一个Behavior,

<行動 名前="マウスの周りに集まる" 頻度="0"><次の行動リスト 追加="false"><行動参照 名前="座ってマウスのほうを見る" 頻度="1" /></次の行動リスト></行動>

主要负责对发生的频度等进行读取。

最后Main::loadConfiguraion()在加载完两个配置文件后调用了如下方法:

this.getConfiguration().validate();
这个确认有效的方法分别去检查ActionBuilder和BehaviorBuilder是否存在问题,即重新确认当前存储的节点是否确实存在于配置文件中的安全检测。到此Main::loadConfiguration()结束。



1 0