(译)如何使用cocos2d来制作一个打地鼠的游戏:第一部分

免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播。同时,转载时不要移除本申明。如产生任何纠纷,均与本博客所有人、发表该翻译稿之人无任何关系。谢谢合作!

  原文链接地址:http://www.raywenderlich.com/2560/how-to-create-a-mole-whacking-game-with-cocos2d-part-1

教程截图:

  在iOS Programming 101课程中,我的一个学生提议,让我写一个教程,关于如何使用cocos2d来制作一个打地鼠的游戏。 

  我认为这是一个非常好的主意,原因有三:

  1. 目前,我的博客上有许多关于cocos2d的教程了,但是,却没有一个教程把制作一个游戏相关的所有细节都放在一起来开发一个游戏。因此,这个教程应运而生了。
  2. 这是一个非常好的机会来介绍一个新的话题:如何制作一个游戏,让它同时在iphone、ipad和支持高清(retina)显示的iphone上面运行。
  3. 当然,最重要的原因是--打地鼠本来就很有趣!

  这个教程建立在下列教程的基础之上:

  如果你还没有阅读上面这些教程,我强烈建议你先读一读。

  这是一个两部分的教程。在第一部分中,我们将会创建基本的游戏框架--有一些可爱的地鼠从洞里面钻出来。我们将会花费大量的时候来讨论如何组织图片资源和坐标计算,以便于你的游戏能够同时运行在iPhone3GS、ipad和高清显示的iphone(其实是就是iphone4)上面,同时还要保证游戏性能!

规划图片资源: 总览

  因为,我们想让这个应用能够同时运行在iphone、retina-display iphone和ipad上面,所以,我们需要花点时间来仔细考虑一下,如何制作图片,主要是图片的尺寸!

  为了理解到底多大的尺寸合适,首先,我们要先讨论下面三个主题:

  • Retina Display and UIKit
  • Retina Display and Cocos2D
  • iPad, iPhone, and Aspect Ratios

Retina Display and UIKit

  普通的iphone和支持高清显示的iphone之间的差别就是支持高清显示的iphone能够显示2倍的像素。因此(在landscape模式下),普通的iphone显示480×320个像素,而支持高清的iphone则可以显示960×640个像素。

  “但是,请等一下”,你可能会问,“如果显示2倍数量的像素,那么不会破坏那些已经写好的假设屏幕大小只有480×320的应用程序吗?” 没错,特别是当你使用UIKit编程的时候,通常都是硬编码frame的大小。但是,如果你在UIKit里面指定frame大小的时候,实际的单位是点,而不是像素。

  在一个普通的iphone上面(不支持retina display),一个点就等于一个像素。但是,在支持retina的iphone上面,一个点等于2个像素。因此,当你指定(10,10),在普通的iphone上面也是(10,10),而在支持retina的iphone上面,则是(20,20),因此,那会是同样的相对偏移量。(因为,像素加倍了,相对于图片来说,相对偏移量相等)。

  当你使用apple的控件或者Core Graphics,Apple已经写了一些代码来确保在Retina显示的时候没有问题。(因为你写代码指定大小都是point为单位)

  唯一的需要注意的就是使用图片的时候。比如你在一个iphone程序中使用了一张200×200的图片,如果你什么也不做,那么retina显示会把这张图的尺寸放大2倍--那样会很难看,如下图所示,会模糊。

  因此,你所需要做的就是为所有的图片提供两种版本,一种普清的,一种高清的。如果在高清图片名字后面增加一个@2x后缀(比如sample@2x.png),那么不管什么时候,你只要调用[UIImage imageNamed:...]或者一些更简单的api,当程序编译到支持高清设备的时候,就会自动加载后缀为@2x的图片。

  因此,制作一个支持Retina显示的UIKit程序是非常容易的,只需要为每一张普清图片同时提供一个高清版本(2倍像素),并且指定为@2x后缀即可。

  那么cocos2d里面该如何做呢?

Retina Display and Cocos2D

  告诉你们一个好消息--最新版本的cocos2d完全支持retina显示,而且使用这个功能只需要下列1-2-3步:

  1. 调用CCDirector的函数enableRetinaDisplay来开启retina显示支持。如果你使用cocos2d模板,那么只需要把app delegate里面对这句函数调用的注释去掉即可!
  2. 往你的程序中添加2倍大小的精灵图片,但是,这里命名不是增加@2x后缀,而是使用-hd(如:sample-hd.png)。当你加载精灵的时候,使用不带-hd后缀的图片名字,当cocos2d被部署到支持retina显示的设备上时,就会自动加载高清的图片。
  3. 当你在cocos2d里面设置精灵坐标的时候,使用的是点,而不是像素。注意:有些api使用的是像素,这些方法中都包含pixel字样,比如position和positionInPixel。InPixels;

  因此,你只需要让美工提供给你高清格式的图片就行了,你可以很容易地使用软件把这些高清图片变得普清图片。比如TexturePacker就可以做这样的事。

  你可能会奇怪,为什么需要准备两种不同格式的图片--为什么不每次都加载高清的图片,然后当需要普清图片的时候,用程序来缩放?好吧,因为把纹理(texture)加载到内存里面是非常消耗内存的操作,如果某个程序被安装在不支持高清显示的设备上面(比如IPOD2),那么你就会占用两倍大小的内存,当然是相比于只加载普清的图片的程序。(因为高清图片像素个数多一倍,所以必须消耗的内存要大。正确的做法就是前面说的,提供两套图片。)

  不用担心,你没有必要在photoshop里面不停地缩放图片的尺寸。TexturePaker里面有一个非常好的功能,可以很容易地通过高清图片得到对应的普清图片,这种方法在后面的教程中会讲到。

iPad, iPhone, and Aspect Ratio

  好,现在处理iphone上面的高清和普清显示非常简单了(准备两套图片),但是,iPad呢?该怎么做呢?

  呃,要想编写一个游戏,让它同时能够在iphone和ipad上面运行似乎是一件非常烦人的事--因为设备的纵横比(aspect ratio)不一样!

  iphone支持480*320或者960*640---因此是1.5的纵横比。然后,iPad是768*1024的--纵横比等于1.33.

  这意味着,如果你有一张图片,能够完全覆盖iPad的背景(768*1024),如果你也想在iPhone里面继续使用它。那恐怕不会匹配那么好。比如,你把它缩小,让宽度等于iphone的宽度(通过乘以0.9375):你会得到720*960,因此,还是会有一部分图像不能显示!

  这让事情变得有点恼火了,因为,不只是背景图片的问题,因为纵横比的不同,原先设置节点使用的坐标就不能够再使用了。

  目前有一些方法可以解决这个问题,下面列举了一些我看过的、听过的和使用过的(如果你们有更好的方法,可以留言):

  • 考虑一个“可玩区域”,这个区域的大小等于640*960,同时把它放置在iPad的屏幕中间。这会使得游戏区域外面有一些空白--但是,你还可以放置一张位置不动、颜色相同的背景图片,那么玩家在玩游戏的时候就很难发现了。这样会使你很容易地在不同的设备之间使用相同的坐标值,并且还可以重用图片资源。这个方法也是我们这个教程里所使用的方法。
  • 你可以使你的iPad的程序的纵横比与iPhone的纵横比一样,在iPad屏幕的边界留出42个像素,把“主要内容”放置在685*1024的范围中。如果,你可以把你的应用中的内容放置在684*1024的“盒子”中,那么你就可以为每一个设备使用缩放图片了。
  • 你可以为iPhone、iPad和Retina display的iPhone提供不同的图片和坐标值。这样的灵活性是最大的,但是,会产生更大的二进制程序文件,并且不同的设备上面的对象位置都需要重新计算。

  另一个原因,cocos2d目前对iPad的支持并不是很友好。只是可以自动加载后缀为“-hd”的图片,转换坐标的工作还是得由你来做。

规划图片资源:结论

  好,基于上面的讨论,下面是这篇教程的图片资源规划:

  • 图片被设计成一个960*640的“可玩区域”大小,在高清的iphone上面就是全屏的,在iPad上面就会居中显示。
  • 高清图片可以通过TexturePacker转换成普清的,并且生成相应的spritesheet。
  • 高清的图片将以“-hd”作为后缀,而普清的图片则没有。cocos2d会根据当前是否支持高清显示而加载相应的图片。
  • 背景是一个特例,因为它总是需要全屏。背景将会做成1024*768的大小(因为iPad屏幕的大小是1024*768)。当这张图片用作iPhone上面的时候,会有一部分图片会超过屏幕边界,但是,在这个教程中,并没有关系。(后面将会看到,背景颜色很单一)
  • iPad版本版本将会包含一些代码,用来加载“-HD”后缀的图片,转换坐标并设置到“可玩区域”内,使用合适的字体大小等。

  首先,下载这篇教程的图片资源,解压,然后看看里面有什么:

  • 在“foreground”文件夹中,前景大小为1024*768(也就是iPad的大小),但是,它实际上被划分为两个部分:上半部和下半部。因为我们将在这两个部分当中放置三个“地鼠洞“,这样就会看起来,地鼠真的是从地底下钻出来的。
  • 在”background“文件夹中,iPad的纵横比是1.33,所以一半是512*384.这是因为,背景实际上是看不到的,只有通过3个地鼠洞才可以看到背景。这么小的范围,完全没有必要加载一张1024*1024的图片。相反,我们加载一张小图片,然后放大。
  • 在"sprites”文件夹中,所有的精灵都被设置成可以放到960*640的“可玩区域”内。注意,还有一个地鼠图片,二个地鼠动画(一个笑的动画,一个被打中时的动画)。

  好了,有足够的背景信息了--是时候开始动手了!、

Getting Started

  打开XCode,选择“File\New Project...",然后选择“User Templates\cocos2d\cocos2d Application”,再点击”Choose...“。把工程命名为WhackAMole,单击Save。

  接下来,把你刚刚下载下来的图片资源文件夹全部拷到工程目录下面。它的位置应该是Classes文件夹的兄弟,如下图所示:

  接下来,确保你已经安装了TexturePacker。如果你还不知道怎么使用它,可以查看译者翻译的另一篇教程

  你可以打开TexturePacker工具,并为这个项目创建所有的sprite sheet。你也可以使用TexturePacker的命令行工具,它可以和XCode集成,并且处理起来更加高效!

  右击Resources,选择”Add\New File...“,选择Mac OS X\Other\Shell Script,再点Next。取名为PackTextures.sh,然后点Finish。

  然后把PackTextures.sh里面的内容替换成下面的代码:

#!/bin/sh
TP
="/usr/local/bin/TexturePacker"
if [ "${ACTION}"="clean" ]
then
echo
"cleaning..."
rm resources
/background*
rm resources
/foreground*
rm resources
/sprites*
else
echo
"building..."
${TP}
--smart-update \
--format cocos2d \
--data resources/background-hd.plist \
--sheet resources/background-hd.pvr.ccz \
--dither-fs \
--opt RGB565 \
Art
/background/*.jpg
${TP} --smart-update \
--format cocos2d \
--data resources/background.plist \
--sheet resources/background.pvr.ccz \
--dither-fs \
--scale 0.5 \
--opt RGB565 \
Art/background/*.jpg
${TP} --smart-update \
--format cocos2d \
--data resources/foreground-hd.plist \
--sheet resources/foreground-hd.pvr.ccz \
--dither-fs-alpha \
--opt RGBA4444 \
Art/foreground/*.jpg
${TP} --smart-update \
--format cocos2d \
--data resources/foreground.plist \
--sheet resources/foreground.pvr.ccz \
--dither-fs-alpha \
--scale 0.5 \
--opt RGBA4444 \
Art/foreground/*.jpg
${TP} --smart-update \
--format cocos2d \
--data resources/sprites-hd.plist \
--sheet resources/sprites-hd.pvr.ccz \
--dither-fs-alpha \
--opt RGBA4444 \
Art/sprites/*.jpg
${TP} --smart-update \
--format cocos2d \
--data resources/sprites.plist \
--sheet resources/sprites.pvr.ccz \
--dither-fs-alpha \
--scale 0.5 \
--opt RGBA4444 \
Art/sprites/*.jpg
fi
exit 0

 

  这个脚本运行TexturePacker来为背景图片、前景图片和精灵图片创建相应的精灵表单(sprite sheet)--同时包含高清的和普清的。

  注意,每一个图片都被保存为pvr.cca格式,因为,这是一种磁盘占用空间小,并且内存消耗也少的图片格式。当然,像素格式和抖动选项也被设置了,这是一个折中,综合考虑内存消耗和图片显示质量。

  如果你对TexturePacker的这些选项的功能不是很清楚,你可以打开终端(Terminal),然后输入TexturePacker -help,那么就会有完整的选项功能列表。

  接下来,你需要做一些设置,在你的工程每次编译的时候,可以运行这个shell脚本。右键点击Targets,选择“Add\New Target...",再选择"External Target”(注意不要选择Shell Script Target!),然后点击Next。把这个Target命名为TexturePacker并点击Finish。

  然后右键点击刚刚建立的这个Target,修改它的设置,如下图所示:

  最后一步,就是为你的程序设置target依赖。双击项目Target,然后找到General标签,点下面的+号,再从弹出的列表中选择TexturePacker Target,并点击Add Target。

  编译你的工程,如果输出结果如下图所示,那么就证明你的配置OK。(注意,上面的这些全是针对XCode3所设置的,XCode4大同小异,只是界面不一样了,原理不变)

  接下来,把刚刚生成的精灵表单和相应的属性列表增加到项目中。右击Resources,选择“Add\Existing Files...”,然后选择background,foreground和sprites文件(总共有12个文件),再点Add.如果做完后,那么看起来会是下图这个样子:

  如果你愿意,你可以双击任何一个.pvr.ccz文件,打开看看,里面到底有什么。你就会发现TexturePacker有多么方便了!

设置背景

  接下来,HelloWorldScene.m文件并找到init方法。删掉自动生成的那4行创建一个label的代码。同时,用下面的代码替换掉:

// Determine names of sprite sheets and plists to load
NSString *bgSheet =@"background.pvr.ccz";
NSString
*bgPlist =@"background.plist";
NSString
*fgSheet =@"foreground.pvr.ccz";
NSString
*fgPlist =@"foreground.plist";
NSString
*sSheet =@"sprites.pvr.ccz";
NSString
*sPlist =@"sprites.plist";
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
bgSheet
=@"background-hd.pvr.ccz";
bgPlist
=@"background-hd.plist";
fgSheet
=@"foreground-hd.pvr.ccz";
fgPlist
=@"foreground-hd.plist";
sSheet
=@"sprites-hd.pvr.ccz";
sPlist
=@"sprites-hd.plist";
}

// Load background and foreground
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:bgPlist];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:fgPlist];

// Add background
CGSize winSize = [CCDirector sharedDirector].winSize;
CCSprite
*dirt = [CCSprite spriteWithSpriteFrameName:@"bg_dirt.jpg"];
dirt.scale
=2.0;
dirt.position
= ccp(winSize.width/2, winSize.height/2);
[self addChild:dirt z:
-2];

// Add foreground
CCSprite *lower = [CCSprite spriteWithSpriteFrameName:@"grass_lower.jpg"];
lower.anchorPoint
= ccp(0.5, 1);
lower.position
= ccp(winSize.width/2, winSize.height/2);
[self addChild:lower z:
1];

CCSprite
*upper = [CCSprite spriteWithSpriteFrameName:@"grass_upper.jpg"];
upper.anchorPoint
= ccp(0.5, 0);
upper.position
= ccp(winSize.width/2, winSize.height/2);
[self addChild:upper z:
-1];

// Add more here later...

 

  好,让我们一个一个来讲解,因为,这里面有许多新的东西。

  1.决定加载的精灵表单和plist文件的名字。这个部分,把TexturePacker生成的精灵表单和plist文件,按照某种方式加载进来了。注意,在iPhone上面,cocos2d会自动地加载“-HD”和不带“HD”的版本,具体取决于是否激活Retina 显示。然后,在iPad上面,它并不会加载"-hd"版本,所以你必须手动告诉它加载,因此,上面我们判断,如果是iPad,那么就加载带"-hd"版本的资源。

  2.加载背景和前景。接下来就是把背景、前景还有精灵都加载到精灵帧缓冲(sprite frame cache)中,以便后面可以使用。注意,目前精灵并没有加载到CCSpriteBatchNode中。

      3.添加背景。背景图片作为当前层的一个孩子被加进去。(Z=-2的话,就可以使任何对象都在它上面,除非其它对象的Z小于-2)。接下来,把这张图片放大两倍,因为,之前,我们为了节约内存,只使用了一半大小的图片。之后,再把图片放置到屏幕中间。

  4.前加前景。前景是当作两个部分加上去的。这里用了一种非常简单的设置精灵图片坐标点的方式,通过设置精灵的锚点为图片的中点,或者底部,或者顶部。通过这种方式,假如你要把一张图片的左下角设置为屏幕的左下角,你只需要先把图片的锚点设置为(0,0),然后设置position为0,0就好了。非常方便!而且,这个坐标值,在所有的设备上都通用。注意,背景部分超出屏幕之外了,但是,这并没有关系,因为用户看不到。还要注意的是,图片增加的时候,使用了不同的Z值,最后面的图片使用最小的Z值。

  编译并运行,你会看到如下的输出。你可以通过模拟器测试,也可以通过真机测试,看看效果到底如何^^。

  如果你尝试在支持高清的设备上运行,你会注意到,图像有模糊,似乎还是使用普清的资源。

  这是因为,我们还有一步没有做完:还需要调用CCDirector的enableRetinaDisplay方法能激活高清显示。

  为了实现这个目的,打开WhackAMoleAppDelegate.m,然后在applicationDidFinishLaunching方法里面,删除那三句注释:

if( ! [director enableRetinaDisplay:YES] )
CCLOG(
@"Retina Display Not supported");

 

  编译并运行,现在,再尝试使用支持高清的设备吧,这时就会加载高清图片资源了。

放置地鼠

  对于这个游戏来说,你将会在场景中添加3只地鼠--每个洞一个地鼠。地鼠会躲在草的下面,但是,会突然从某个洞里冒出来,这样你就可以打它了。

  首先,让我们为每个洞的下面添加一只地鼠。我们会先让它们在所有的图片上面,这样,可以确保位置对了,然后通过修改Z值,就能够把它们隐藏到草地后面去了。

  打开HelloWorldScene.h,然后增加一个数组来保存当前关卡的地鼠,如下所示:

// Inside @interface HelloWorld
NSMutableArray *moles;

  通过保存地鼠的数组,我们可以很容易地遍历每一只地鼠。

  接下来,在init方法中添加一些代码来放置地鼠(添加位置位于 “Add more here later…”注释后面),如下所示:

// Load sprites
CCSpriteBatchNode *spriteNode = [CCSpriteBatchNode batchNodeWithFile:sSheet];
[self addChild:spriteNode z:
999];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:sPlist];

moles
= [[NSMutableArray alloc] init];

CCSprite
*mole1 = [CCSprite spriteWithSpriteFrameName:@"mole_1.jpg"];
mole1.position
= [self convertPoint:ccp(85, 85)];
[spriteNode addChild:mole1];
[moles addObject:mole1];

CCSprite
*mole2 = [CCSprite spriteWithSpriteFrameName:@"mole_1.jpg"];
mole2.position
= [self convertPoint:ccp(240, 85)];
[spriteNode addChild:mole2];
[moles addObject:mole2];

CCSprite
*mole3 = [CCSprite spriteWithSpriteFrameName:@"mole_1.jpg"];
mole3.position
= [self convertPoint:ccp(395, 85)];
[spriteNode addChild:mole3];
[moles addObject:mole3];

  首先,为精灵创建一个CCSpriteBatchNode,这样就可以更加高效地渲染这三只地鼠了。(每次只需要一个OpenGL call),并把batchNode当作当前层的一个孩子加进去。注意,Z值被临时设置成999,因此,地鼠就会显示在所有的图片之前,那样我们就可以判断地鼠位置是否正确。

  然后从精灵帧缓冲中加载所有的精灵。接着,为每一个地鼠创建一个精灵,把它们放置在场景中,同时添加到数组里面。注意,每个地鼠的坐标是在一个480*320的“可玩区域”内(iphone屏幕的大小)。对于iPad来说,这些点需要做一些转换,因此,我们定义了一个辅助函数convertPoint。接下来,在Init方法的上面加入下列代码:

- (CGPoint)convertPoint:(CGPoint)point {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
return ccp(32+ point.x*2, 64+ point.y*2);
}
else {
return point;
}
}

  这个方法把“可玩区域”内的一个点转换成合适的iPad屏幕上面的点。记住:

  • 我们在iPad上面使用的是高清的图像,所有的像素点都要加倍。
  • 我们把960*640的区域居中放在1024*768的iPad屏幕上,因此,左右两边要留下32个像素的margin,上下两边要留下64个像素的margin。

  因此, 这个方法只是做了一些简单的数学计算,使得坐标在iPad上面不会出问题。

  还有一件事---在忘记之前,我们需要做一些内存清理操作,找到dealloc方法,添加下列代码:

[moles release];
moles
= nil;

 

  编译并运行,你将看到3只地鼠放置在正确的位置上了!你因此同时测试一下iPhone,Retina iPhone和iPad,确保每个设备上看起来都没问题。

弹出地鼠

 现在,我们确定地鼠的位置没有问题了,接下来,让我们添加一些代码,让他们能够从地下钻出来吧!

  第一件事,把Z值,从999改成0,这样的话,地鼠就会在地底下了。

  改完之后,在init方法的后面添加下面的代码:

[self schedule:@selector(tryPopMoles:) interval:0.5];

  如果之前你没有见过这样的用法,你可以调用其它的方法,比如schedelUpdate。但是,这个例子中,我们只想让地鼠每隔0.5秒钻出一个。

  接下来,增加tryPopMoles方法:

- (void)tryPopMoles:(ccTime)dt {
for (CCSprite *mole in moles) {
if (arc4random() %3==0) {
if (mole.numberOfRunningActions ==0) {
[self popMole:mole];
}
}
}
}

 

  这个方法会每隔0.5秒调用一次,每一次它都会循环判断每一个地鼠,让它有1/3的机会从洞里面钻出来。但是,如果它已经钻出来,也就是运行的action的个数不等于0的话,那么也不会再钻了。

最后,实现popMole:方法:

- (void) popMole:(CCSprite *)mole {
CCMoveBy
*moveUp = [CCMoveBy actionWithDuration:0.2 position:ccp(0, mole.contentSize.height)]; // 1
CCEaseInOut *easeMoveUp = [CCEaseInOut actionWithAction:moveUp rate:3.0]; // 2
CCAction *easeMoveDown = [easeMoveUp reverse]; // 3
CCDelayTime *delay = [CCDelayTime actionWithDuration:0.5]; // 4

[mole runAction:[CCSequence actions:easeMoveUp, delay, easeMoveDown, nil]];
// 5
}

 

  这个代码,使用cocos2d的action来制作弹出洞的效果,先弹出来,然后暂停半秒,再钻回去。让我们一行一行地解释:

  1. 创建一个action,让地鼠沿着Y轴钻洞来。因为,之前我们放置地鼠的位置是正确的,所以看起来,就像是从洞下面钻出来一样。
  2. 为了使得移动更加真实,上面的action用一个CCEaseInOut action包装起来了。这会得钻洞的动作在开始和结束的时候速度比较慢,看起来,就好像地鼠在加速和减速一样。
  3. 创建一个action,使地鼠能够钻回来。这里通过调用action的reverse方法,能得到相反的action.
  4. 创建一个action,在地鼠钻出来以后,能够暂停1秒。
  5. 现在,action可以运行了。通过用CCSequence把这3个action包装起来,那样它们就可以按顺序执行了。

就这么多!编译并运行代码,你将会看到3只地鼠交替地钻出洞来,cool吧?

何去何从?

  本教程完整源代码

  期待这个教程的后续吧,在那个教程里,我们会添加很酷的动画,同时会添加一些游戏逻辑,使得你可以打地鼠并且得分。当然,还有非常好听的音乐。

  译者的话:翻译如果有语法错误还忘指出,最近时间比较紧,校对工作没时间仔细做了,欢迎大家不吝指出!

 

著作权声明:本文由http://www.cnblogs.com/andyque翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!

posted on 2011-05-15 21:04  子龙山人  阅读(...)  评论(... 编辑 收藏

统计