FunCodeC实验教学指南入门篇02FunCodeC实验教学指南_第1页
FunCodeC实验教学指南入门篇02FunCodeC实验教学指南_第2页
FunCodeC实验教学指南入门篇02FunCodeC实验教学指南_第3页
FunCodeC实验教学指南入门篇02FunCodeC实验教学指南_第4页
已阅读5页,还剩87页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

实验教学实验:海底世界来回游动学习目标用代码响应“精灵与世界边界碰撞”事件,即OnSpriteColWorldLimit函数;如何让精灵调头运动;复习选择结构;学习应用SetSpriteFlipX和SetSpriteFlipY函数:学习应用strcmp函数。在“入门”一章中,我们学会了如何设计游戏界面,也知道简单地编ー点小程序。从这章开始,我们开始用代码写稍微复杂ー些的小游戏。在这个实验中,我们还是使用seafish项目。到目前为止,我们能让yellowfish游动了,但是它游出屏幕以后就不再回来了。现在,我们要求它游到屏幕左边界时,就调头向右游动;游到右边界时,就调头向左游动。Stepl:资源导入前面已经导入,这里不再需要继续导入Step2:funcode接口〃由CSystem类定义//OnSpriteColWorldLimit:精灵与世界边界碰撞后将被调用的函数,可在此函数体里(Main.cpp)增加自己的响应代码/Z精灵之间要产生碰撞,必须在编辑器或者代码里设置精灵的世界边界限制//参数szName:碰撞到边界的精灵名字//参数iColSide:碰撞到的边界〇左边,1右边,2上边,3下边staticvoidOnSpriteColWorldLimit(constchar*szName,constintiColSide);〃由CSprite类定义//SetSpriteFlipX:设置精灵图片X方向翻转显示//参数bFlipX:true翻转false不翻转(恢复原来朝向)voidSetSpriteFlipX(constboolbFlipX);//illCSprite类定义//SetSpriteFlipY:设置精灵图片Y方向翻转显示/Z参数bFlipY:true翻转false不翻转(恢复原来朝向)voidSetSpriteFlipY(constboolbFlipY);〃标准库中定义〃当sl=s2时,函数返回值为〇。externintstrcmp(constchar*sl,constchar*s2);Step3S思路阐述在“入门”一章,我们已经学习了精灵与世界边界碰撞事件(具体查看“项目改进”之“精灵与世界边界的碰撞")。将yellowfish的世界边界设计得与可见区域重合,然后将“限制模式”改为NULL,该选项表示,精灵与世界边界碰撞时,引擎会调用相应函数进行响应。最后,在FunCode中保存这些修改。WinMain函数中,每次循环调用EngineMainLoop函数(该函数为引擎主循环函数,由系统调用,不可自己调用)时,引擎会检查是否有精灵与世界边界碰撞事件发生。如果有,再检测限制模式是否是NULL。如果是NULL,就调用OnSpriteColWorldLimit函数并执行函数内部代码。所以处理yellowfish的来回游动应该在该函数中添加相应的代码,即当鱼碰撞边界时,速度方向取反。精灵虽然反向游动了,但是头还没有调转。这就需要SetSpriteFlipX和SetSpriteFlipY了,用来将精灵沿着X轴或丫轴调转。这个实验中,目前只对yellowfish进行处理。但是在以后的实验中,我们会对多个精灵进行处理。因此,我们在处理某个精灵时,有必要根据精灵名称先进行判断。我们要用到strcmp函数对精灵名称进行判断,即当与边界碰撞的是yellowfish时,才进行相应处理。该函数是C语言标准库函数,所在头文件为String,h,所以我们需要在Main,cpp顶部增加一句:^include"String,h”;Step4:代码实现在前面yellowfish的基础上完成本实验。C++是面向对象的语言,因此相关功能的实现,最好通过特定对象调用适当的方法来完成。在我们的实验中CGameMain是游戏主管类,因此我们就在其内部定义我们需要的方法,然后在Main,cpp中引用CGameMain的方法来实现我们需要的功能。因为,处理Main,cpp处理碰撞的函数是OnSpriteColWorldLimit,为了同他保持一致,我们同样在CGameMain定义同名的方法,即在LessonX.h的public中声明:void GameMainLoop(floatfDeltaTime);void Gamelnit();void GameRun(float fDeltaTime );void GameEnd();void OnSpriteColWorldLimit( const char*szName,constintiColSide);接着,我们需要在LessonX.cpp中定义该方法://0左边1右边2上边3下边voidCGameMain::OnSpriteColWorldLimit(constchar*szName,constintiColSide){if(strcmp(szName,"yellowfish")==0){ //与边界发生碰撞的是yellowfishif(iColSide==1){//碰到右边mySprite->SetSpriteLinearVelocityX(-10.f)J〃向左游,X坐标减少,速度为负}elseif(iColSide==0){ //碰到左边mySprite->SetSpriteLinearVelocityX(10.f): 〃向右游,X坐标增大,速度为正)I再修改yellowfish的初始化速度,即Gamelnit函数中的mySprite->SetSpriteLinearVelocity(10,-5);修改为 mySprite->SetSpriteLinearVelocity(10,〇);〃初始化速咬为正,向右游动最后再Main.cpp的OnSpriteColWorldLimit中添加调用CGameMain我们刚定义的方法,即:voidCSystem::OnSpriteColWorldLimit(constchar*szName,constintiColSide){gGameMain.OnSpriteColWorldLimit(szName,iColSide);思考:g_GameMain是什么?为什么在Main,cpp中可以使用?编译运行,观察结果,看看yellowfish在同边界碰撞后有没有问题。同学们一定发现,当yellowfish同右边界碰撞后,向左游动的过程中,没有掉头,这与生活经验不符,因此我们还需要添加掉头函数SetSpriteFlipXo修改LessonX.cpp的OnSpriteColWorldLimit函数,添加入掉头部分:if(strcmp(szName,“yellowfish")=0){if(iColSide==1){mySprite->SetSpriteLinearVelocityX(-10.f);mySprite->SetSpriteFlipX(truc):〃向左游需要掉头}elseif(iColSide==0){mySprite->SetSpriteLinearVelocityX(10.f);mySprite-〉SetSpriteFlipX(false);})思考:mySprite->SetSpriteFlipX(false);的作用又是什么编译运行,可以看到yellowfish可以正常的来回游动了:

练习:1、如果要求yellowfish游出屏幕,但很快又游回来应该如何处理。键盘控制鱼游动ー学习目标用代码响应“键盘按下”和“键盘弹起”事件,即OnKeyDown和OnKeyUp;了解键盘常量;复习switch-case语句;初步了解调试;在这个实验中,我们还是使用seafish项目。到目前为止,我们能让yellowfish游动并且掉头了,但是却无法控制鱼的走向,本节中我们将学习如何通过键盘的WSAD来控制yellowfish的上下左右移动。Stepl 资源导入:添加如图所示的鱼,并起名bluefish,速度设为0,如下图V程序接口bluefish速度 X0.000Y0.000Step2:FunCode对应的接口首先我们来学习键盘的常量定义,打开CommonClass.h,找到enumKeyCodes,其中定义了键盘上的按键对应的常量定义。这里说明常用的按键及其命名规则所有的按键都以KEY_开头,然后跟上相应键所对应标识,常用的介绍如下:英文字母与数字:变量 键盘上的键KEY_A AKEY_B B

KEY1KEY2键盘上的键BACKSPACETAB键盘上的键BACKSPACETABENTERCONTROLALTSHIFTESCSPACE这节中我们要用到的显然就是KEYW,KEYS,KEYA,KEYD要想用WSAD来控制小鱼,那么必须捕捉键盘的输入,在funCode中,我们利用OnKeyDown来捕捉按键按下的操作,OnKeyUp来捕捉按键放开的操作定义如下://CSystem中定义//OnKeyDown:键盘被按下后将被调用的函数,可在此函数体里(Main.cpp)增加自己的响应代码//参数iKey:被按下的键,值见enumKeyCodes宏定义//参数bAltPress,bShiftPress,bCtrlPress:键盘上的功能键Alt,Ctrl,Shift当前是否也处于按下状态staticvoidOnKeyDown(constintiKey,constboolbAltPress,constboolbShiftPress,constboolbCtrlPress);〃CSystem中定义//OnKeyUp:键盘按键弹起后将被调用的函数,可在此函数体里(Main,cpp)增加自己的响应代码//参数iKey:弹起的键,值见enumKeyCodes宏定义staticvoidOnKeyUp(constintiKey);Step3:思路阐述我们需要定义ー个精灵いprite来表示bluefish,之后的操作均在该对象上进行。利用WSAD来控制鱼的上下左右移动,那么我们在按下相应的按键时,需要给鱼儿设置ー个对应方向的速度。如按下牝设置ー个向左的速度。同时当松开按键时其对应方向的速度应该置为〇。键盘按下的响应函数是OnKeyDown,松开则是OnKeyUp。Step4:代码详解首先在LessonX.h中声明表示bluefish的精灵指针变量,并在LessonX.cpp的CGameMainO构造函数中创建该实例,分别如下:CSprite *bluefish;bluefish=newCSprite("bluefish);同“来回游动”中的我们定义的OnSpriteColWorldLimit类似,我们在LessonX.h声明OnKeyDown与OnKeyUp函数:思考:如果不加CGameMain::会怎么样?voidCGameMain::OnKeyDown(constintiKey,constboolbAltPress,constboolbShiftPress,constboolbCtrlPress);void OnKeyUp(constintiKey);并在LessonX.cpp中添加各自的定义,先是OnKeyDown:void OnKeyDown(constintiKey,constboolbAltPress,constboolbShiftPress,constboolbCtrlPress){f1oatfSpeedUp=0,fSpeedDown=0,fSpeedLeft=0,fSpeedRight=0;switch(iKey)■caseKEYW: 〃W向上fSpeedUp=-10.f;break;caseKEY_A: 〃A向左fSpeedLeft=-15.f;break;caseKEYS: 〃S向下fSpeedDown=10.f;break;caseKEY_D: 〃D向右fSpeedRight=15.f;break;}if((fSpeedLeft+fSpeedRight)>0)〃如果向左则要转向bluefish->SetSpriteFlipX(false);(ゝ丨seiI((FSpeedLeft+fSpeedRight)く0)〃如果向右则不转向bluefish->SetSpriteFlipX(true);思考:为什么要这样对bluefish设置速度?bluefish->SetSpriteLinearVelocity(fSpeedLeft+fSpeedRight,fSpeedUp+fSpeedDown);}然后在OnKeyUp中添加松开松开按键的响应函数voidCGameMain::OnKeyUp(constintiKey){〃如果松开的是WSAD,则置鱼的速度为〇if(iKey==KEY_W||iKey==KEY_A||iKey==KEY_S||iKey==KEY_D)bluefish->SetSpriteLinearVelocity(0,0);}最后在Main.cpp的对应函数中引用我们的函数,请同学们自己引用。编译运行,我们可以通过WSAD来控制鱼的上下左右移动了:108108109110111112113114116116117118119120121122123124126126127128129130131技巧:调试初步程序中如果出了问题,检查代码又不容易检查出来,那么最好的方法就是调试了。调试可以让程序员手动控制代码的运行进度,进而发现问题的所在,并最终解决问题。为了简单说明调试寻找错误的过程,这里我们故意修改一行代码:在LessonX.cpp的OnKeyDown中if((fSpeedLeft+fSpeedRight)>0)bluefish->SetSpriteFlipX(false);elseif((fSpeedLeft+fSpeedRight)<0)bluefish->SetSpriteFlipX(true);我们不妨将最后行中的bluefish->SetSpriteFlipX(true);修改为:bluefish->SetSpriteFlipX(false);编译运行,当我们按下A键时发现bluefish不再掉头!接着我们就尝试通过调试来发现问题所在。首先需要设置断点。断点,调试器的功能之一,可以让程序中断在需要的地方,从而方便其分析。也可以在一次调试中设置断点,下一次只需让程序自动运行到设置断点位置,便可在上次设置断点的位置中断下来,极大的方便了操作,同时节省了时间。因为是当我们操作键盘时出现问题的,所以我们在OnKeyDown上加入断点(双击侧栏),如图所示:-voidCGaaieMain::OnIeyDovn(corurtint'Key,constboolbAltPress,constboolbShiftPress,constboolbCtrlPress){IfloatfSpeedUp=0,fSpeedDown=0,fSpeedLeft=0,fSpeedRight=0.switch(iKey)(caseKEYJ:fSpeed%)=-lO.f;break;caseKEY_A:fSpeedLeft--15.f;break;caseKEY_S:fSpeedDovn=10.f.break;caseKEY.D:fSpeedRight=15.f;break;>if((fSpeedLeft+fSpeedRight)>0)bluefish->SetSpnteFlipX(false).elseif((fSpeedLeft+fSpeedRight)<0)bluefish->SetSpriteFlipX(false):bluefish->SetSpriteLinearVelocity(fSpeedLeft+fSpeedRight,fSpeedUp+fSpeedDown).

边上的小红点就是我们加入的断点,当程序运行时它会运行到该断点等待我们的操作。按F5开始调试。因为问题是出在向左游的时候,所以我们直接按下A键。这时系统会捕捉到按键操作,进入OnKeyDown函数,并发现我们所设置的断点,并停在断点等待我们的指示,如图:107)1080109110;107)1080109110;111112113;114115116;117118.119120;121122123;124125126;127128.129130;131iw-voidCGaaeMain::OnKeyDown(constintiKey,constboolbAltPress,constboolbShiftPress,constboolbCtrlPress){floatfSpeedUp=0,fSpeedDovn=O,fSpeedLeft-O,fSpeedRight=0;switch(iKey)caseKEY_¥:fSpeedUp=-lO.f.break;caseKEY一A:fSpeedLeft=-15.f;break;caseKEY一S:fSpeedDovn=10.f;break;caseKEY一D:fSpeedRight=15.f;break;if((fSpeedLeft+fSpeedRight)>0)bluefish->SetSpriteFlipX(false);elseif((fSpeedLeft+fSpeedRight)<0)bluefish->SetSpriteFlipX(false);bluefish->SetSpriteLinearVelocity(fSpeedLeft+fSpeedRight,fSpeedU)+fSpeedDo»n);108!108!J1097110111;112;113;1141151116117:118119120!121:122;123124125126127128129130)131'小红点上多了一个箭头,说明系统运行到这里。1 ,"喧さ、[是调试的工具栏。.表示“继续”,即跳过当前断点;巴表示“暂停”,即停止调试,并关闭运行程序J表示“继续下ー个过程”,即以过程为单位向下跳转这里我们要用到的就是第三个,快捷键是F10。按下F10。-voidCGaneMam::0nKeyDovn(constintiKey,constboolbAltPress,constboolbShiftPress,constboolbCtrlPress){floatfSpeedUp=0,fSpeedDown=0,fSpeedLeft=0,fSpeedRight=0;Iswitch(iKey)caseKEY_I:fSpeedUp=-lO.f.break;caseKEY_A:fSpeedLeft=-15.f;break;caseKEY.S:fSpeedDovn=lO.f.break;caseKEY.D:fSpeedRight=15.f;break;if((fSpeedLeft+fSpeedRight)>0)bluefish->SetSpriteFlipX(false);elseif((fSpeedLeft+fSpeedRight)<0)bluefish->SetSpriteFlipX(false);bluefish->SetSpriteLinearVelocity(fSpeedLeft+fSpeedRight,fSpeedUp+fSpeedDown).我们发觉黄色箭头向下移动了一格,说明程序在我们的控制下前进了一步。这时我们把鼠标移动到变量上方悬浮一会,可以看到该变量的值。继续按F10,直到他进入elseif,如图1241 }1251if((fSpeedLeft+fSpeedRight)>0)126 bluefish->SetSpriteFlipX(false);1271 elseif((fSpeedLeft+fSpeedRight)<0)bluefish->SetSpriteFlipX(false);1291bluefish->SetSpriteLinearVelocity(fSpeedLeft+fSpeedRight,fSpeedUp+fSpeedDown).13111这时我们发现,当bluefish向左游动时,其翻转值依旧为false,即没有翻转!我们将其改为true,使之实现翻转即可。最后我们撤去断点(在断点上双击),编译运行,问题解决!练习:1,用方向键控制bluefish上下左右游动。键盘控制鱼游动二学习目标掌握类成员变量和局部变量;在上一节中,我们用WASD键来控制bluefish上下左右游动。但是,我们发现bluefish只能是直线运动,不能斜着游动。原因在哪里?我们如何让bluefish也能斜着游动呢。Stepl 资源导入不需要导入新的资源Step2S知识储备类成员变量和局部变量简答的说,类成员变量是在这个类中都可以使用的变量,只要这个类没有被销毁,那么该变量一直存在且可以引用。在C++中类的成员变量往往在private中声明,表示私有变量,仅自己可以引用该变量:当然也可以在public中声明,表示公有变量,此时任何其他类均可以引用该变量。而局部变量只是在局部范围内使用的变量,当离开了这个范围那么,该变量就会被自动销毁,不能再利用。使用的接口与上述实验相同。Step3:思路阐述WSAD控制鱼的上下左右移动相信同学们都已经了解了。然而在上述实验中,如果同时按下WD为何鱼不会向斜向上的方向移动呢。这就需要结合局部变量和类成员变量的知识了。floatfSpeedUp=0,fSpeedDown=0,fSpeedLeft=0,fSpeedRight=〇;都是局部变量,当每次进入函数时,他们都会被重新初始化为0,离开时则被销毁。所以当同时按下WD时,OnKeyDown就会被调用两次,所以他无法保存前一次的速度值,因此速度不可能进行加成,只能是上下左右了。我们将速度替换为CGameMain类的成员变量,他们的作用域是该类的生存周期,这样就可以保存之前的速度值了,那么是否就可以实现斜上斜下方等操作呢。我们不妨来尝试一下。Step4;代码详解先将前面控制鱼游动的代码注释掉,如下:在OnKeyDown中:AfloatfSpeedUp=0,fSpeedDown=0,fSpeedLeft=0,fSpeedRight=0;switch(iKey){caseKEY_W:fSpeedUp=-10.f;break;caseKEYA:fSpeedLeft=-15.f;break;caseKEY_S:fSpeedDown=10.f;break;caseKEYD:fSpeedRight=15.f;break;}if((fSpeedLeft+fSpeedRight)>0)bluefish->SetSpriteFlipX(false);elseif((fSpeedLeft+fSpeedRight)<0)bluefish->SetSpriteFlipX(false);bluefish->SetSpriteLinearVelocity(fSpeedLeft+fSpeedRight,fSpeedUp+fSpeedDown);*/OnKeyUp中/*if(iKey==KEY_W:iKey==KEY_AI|iKey=KEY_S||iKey==KEY_D)bluefish->SetSpriteLinearVelocity(0,0);*/接着在LessonX.h的private中声明类成员变量,分别表示上下左右四个方向的速度:float mfSpeedUp;float mfSpeedDown;float m_fSpeedLeft;float m_fSpeedRight;思考:变量名中的m的意思是什么?在Gamelnit中初始化这四个变量的值:mfSpeedDown=0.f;mfSpeedUp=0.f;mfSpeedRight=0.f;m_fSpeedLeft=0.f;接着,在OnKeyDown中添加由类成员变量控制的速度代码:switch(iKey)caseKEY_W:mfSpeedUp=-10.f;break;caseKEYA:m_fSpeedLeft=-15.f;break;caseKEY_S:mfSpeedDown=10.f;break;caseKEY_D:mfSpeedRight=15.f;break;if((m(fSpeedLeft+mfSpeedRight)>0)bluefish->SetSpriteFlipX(false);elseif((mfSpeedLeft+mfSpeedRight)<0)bluefish->SetSpriteFlipX(true);bluefish->SetSpriteLinearVelocity(mfSpeedLeft+mfSpeedRight,mfSpeedUp+m_fSpeedDown);原理与前面相同,但是这里使用的是类成员变量,可以保存之前的记录。然后在OnKeyUp添加如下代码:〃当松开不同的键时,置对应方向的速度值为〇if(iKey==KEY_W){mfSpeedUp=0.f;}elseif(iKey==KEY_S){mfSpeedDown=0.f;Jelseif(iKey==KEY_A){m_fSpeedLeft=0.f;Jelseif(iKey==KEY_D){mfSpeedRight=0.f;)〃重新设置鱼的速度值if((m_fSpeedLeft+m_fSpeedRight)>0)bluefish->SetSpriteFlipX(false);elseif((m_fSpeedLeft+m_fSpeedRight)<0)bluefish->SetSpriteFlipX(true);bluefish->SetSpriteLinearVelocity(m_fSpeedLeft+m_fSpeedRight,m_fSpeedUp+mfSpeedDown);编译运行,即可实现鱼的斜向游动了。用鼠标控制鱼游动学习目标用代码响应“鼠标滑动”事件,即OnMouseMove函数学习使用dSetSpritePosition函数;学习使用dGetSpritePosition函数学习使用dSpriteMoveTo函数;学习使用sqrt函数;用类成员变量作条件判断在上一节中,我们利用类成员变量实现了鱼的斜线方向上的移动,至此键盘操作控制鱼游动己基本实现。接下来我们来实现用鼠标来控制鱼的移动,要点如下:1、鼠标移动到哪里,鱼就移动到哪里。2、根据鼠标在鱼的前后方,控制鱼调头;3、鱼游动的速度,根据鼠标移动的快慢而快慢;4、程序刚启动时,鱼的位置出现在鼠标位置上。Stepl 资源导入添加一条新的鱼,如下图:起名为followMouseFish,初始速度为〇,世界边界参数为STICKY思考:世界边界设为STICKY的含义是什么?Step2:Funcode接口//CSystem中定义//OnMouseMove:鼠标移动后将被调用的函数,可在此函数体里(Main.cpp)增加自己的响应代码//参数fMouseX,fMouseY:为鼠标当前坐标staticvoidOnMouseMove(constfloatfMouseX,constfloatfMouseY);//CSprite中定义//SpriteMoveTo:让精灵按照给定速度移动到给定坐标点//参数fPosX:移动的目标X坐标值//参数fPosY:移动的目标Y坐标值//参数fSpeed:移动速度//参数bAutoStop:移动到终点之后是否自动停止 void SpriteMoveTo(constfloatfPosX,constfloatfPosY,constfloatfSpeed,constboolbAutoStop);//CSprite中定义//GetSpritePositionX:获取精灵X坐标/Z返回值:精灵的X坐标floatGetSpritePositionXO;//GetSpritePositionY:获取精灵Y坐标//返回值:精灵的Y坐标floatGetSpritePositionYO;//CSprite中定义//SetSpritePositionX:只设置精灵X坐标//参数fPosX:X坐标voidSetSpritePositionX(constfloatfPosX);//CSprite中定义//SetSpritePositionY:只设置精灵Y坐标/Z参数fPosY:Y坐标voidSetSpritePositionY(constfloatfPosY);//CSprite中定义//SetSpritePosition:设置精灵位置/Z参数fPosX:X坐标/Z参数fPosY:Y坐标voidSetSpritePosition(constfloatfPosX,constfloatfPosY);〃标准库文件cmath中定义//SquareRootCalculations(平方根计算),计算value的平方根doublesqrt(doublevalue);Step3思路阐述要想让鱼跟着鼠标移动,必须捕捉鼠标的移动位置,通过OnMouseMove可以捕捉到鼠标在窗口中的位置,然后可以通过设置SpriteMoveTo来让鱼向目标点移动。当鼠标在鱼的后方时需要调头,可以通过GetSpritePositionX来获得鱼当前的位置,并和鼠标的fMouseX进行对比判断鱼和鼠标的相对关系,进而利用前面的SetSpriteFlipX来设置是否调头。至于使鱼初始阶段停留在鼠标上以及根据鼠标移动的快慢来控制鱼的速度请参见Step4oStep4:代码详解使鱼跟着鼠标运动需要在Main.cpp中的OnMouseMove里添加代码,有了前面来回游动和键盘控制鱼游动的例子,想必同学们对如何添加自己的代码的步骤有了一定了解。所以从这里开始就默认同学们对程序的框架有了一定了解,不再进行非常详细的说明了。先声明followMouseFish:CSprite *f〇11owMouseFish;这里我们做个试验,不妨将创建该精灵的任务放在Gamelnit中进行,在Gamelnit中创建该变量,如下:followMouseFish=newCSprite("followMouseFish);再声明OnMouseMove函数,如下:void OnMouseMove(constfloatfMouseX,constfloatfMouseY);

之后定义该函数:voidCGameMain::OnMouseMove(constfloatfMouseX,constfloatfMouseY){followMouseFish->SpriteMoveTo(fMouseX,fMouseY,30,true)ノ/使鱼跟着鼠标移动,30为速度可以自定义:思考:SpriteMoveTo里的参数true是什么意思?最后,在Main.cpp中添加调用代码:gGameMain.OnMouseMove(fMouseX,fMouseY);编译通过,运行时却发生错误!!这是为什么呢?!仔细检查代码我们发现,OnMouseMove中followMouseFish的对象创建是在Gamelnit中,如果在Gamelnit之前就发生OnMouseMove事件,那么此时followMouseFish就还没有创建,那么自然会发生错误。由于在启动游戏过程中或多或少总会发生鼠标的移动(比如将鼠标移动到“开始”按钮处点击运行),所以会触发OnMouseMove事件,而此时Gamelnit却还没有运行!所以应该将followMouseFish的初始化放在OnMouseMove执行前!最理想的地方就是构造函数了!在Gamelnit中剪切foilowMouseFish=newCSprite(z,followMouseFish/z);并将其置于构造函数中:CGameMain::CGameMain()(m_iGameState=1;mySprite=newCSpriteiyellowfish);bluefish=newCSprite("bluefish");followMouseFish=newCSprite(〃followMouseFish");)编译运行,不再报错,同时可以发现鱼跟着鼠标移动,但是鼠标在鱼后方时鱼却没有调头!修改OnMouseMove中的代码,在followMouseFish-〉SpriteMoveTo(fMouseX,fMouseY,30,true);前面添加:floatfFishX=followMouseFishー)GetSpritePositionX(); 获取鱼此时的X坐if(fMouseXくfFishX){ 〃比较鼠标和鱼的相对位置关系followMouseFish->SetSpriteFlipX(true);}else{followMouseFish->SetSpriteFlipX(false);)编译运行即可实现调头。接着我们实现,根据鼠标移动的快慢设置鱼移动的速度思路:要判断鼠标移动的快慢需要一个参照物,这里我们不妨利用鱼为参照点。只要在鼠标移动时算出鼠标和鱼之间的距离d:d越大,鼠标移动的速度越大,d越小,鼠标移动的速度越小。进而可以通过d来自定义设置鱼移动的速度。这里不妨设为fFishSpeed二5*sqrt(d).代码实现:在前面的基础上,在OnMouseMove中的fFishX下添加:floatfFishY=followMouseFish->GetSpritePositionY();floatdistance=sqrt((fMouseX-fFishX)*(fMouseX-fFishX)+(fMouseY-fFishY)*(fMouseY-fFishY));floatspeed=5*sqrt(distance);思考:距离是如何计算的?注:其中sqrt为标准库函数中的开方,需要在文件中引入头文件#include<cmath>其中math开头的c表示该函数库是c中定义的:然后修改followMouseFish->SpriteMoveTo(fMouseX,fMouseY,30,true);中的速度值ユ将30替换为speed:followMouseFish->SpriteMoveTo(fMouseX,fMouseY,speed,true);接着,编译运行即可。最后我们实现,开始运行时鱼的位置处于鼠标位置上思路:鼠标位置只能在OnMouseMove中捕捉,我们可以通过SetSpritePosition来设置鱼的初始位置;在OnMouseMove中的floatfFishX=foilowMouseFish->GetSpritePositionX();上方添加:followMouseFish->SetSpritePosition(fMouseX,fMouseY); 〃使鱼的位置在鼠标所在位置上编译运行!开始时鱼的确和鼠标在ー起,然而不管如何移动鼠标,鱼总是和鼠标在ー起!前面的效果都消失了!有问题!问题是什么呢?问题在于,我们只是让程序启动时让鱼和鼠标在ー起,而不是总是在ー起。我们可以利用m_iGameState来控制游戏状态,观察GameMainLoop中的m_iGameState状态变化,发现游戏正常运行时,其值一直为2.我们添加一个m_iGameState==3的case,用来区分2,即m_iGameState==2时,表示第一次进入游戏,此时执行SetSpritePosition,并置m_iGameState为3;m_iGameState==3时,游戏已经运行,不需要再调用SetSpritePositionOnMouseMove中,修改:followMouseFish->SetSpritePosition(fMouseX,fMouseY);为if(m_iGameState=2){followMouseFish_>SetSpritePosition(fMouseX,fMouseY);m_iGameState=3;}思考:为什么之后不会再调用followMouseFish->SetSpritePosition(fMouseX,fMouseY)?同时在GameMainLoop中添加,case3的情况:if(true){GameRun(fDeltaTime);)else(SetGameState(0);GameEnd();})编译运行,可以发现刚打开游戏时,鱼的位置和鼠标ー样。完成本实验后的效果如下:复制多条鱼学习目标从模板复制出多个精灵,CloneSprite函数;复制精灵时,精灵的命名规则,MakeSpriteName函数;随机数的应用,RandomRange函数;获取当前精灵的名称,GetName函数;C++Vector的使用作为海底世界的最后ー个实验,我们要实现复制多条鱼。我们新建一个rockfishTemplate,并将其拖到主窗口外面。然后复制该模板得到3条同样的鱼,并命名为rockfishO,rockfishl,rockfish2o最后为他们随机设置ー个纵向坐标Y»让他们来回游动!Stepl资源导入如下图所示,添加rockfish,取名为rockfishTemplate,并将其拖出窗口,并设置其世界边界为NULLStep2FunCode接口//CSprite中定义//CloneSprite!复制(创建)一个精灵。精灵的创建方式:先在地图中摆放一个精灵做为模板,设置好各项参数,然后在代码里使用此函数克隆一个实例/Z返冋值:true表示克隆成功,false克隆失败。失败的原因可能是在地图中未找到对应名字的精灵//参数szSrcName:地图中用做模板的精灵名字bool CloneSprite(constchar*szSrcName);〃位于CSystem类中//RandomRange:获取ー个位于参数1到参数2之间的随机数//返回值:int,范围iMin-iMax//参数iMin:小于iMax的整数//参数iMax:大于iMin的整数staticintRandomRange(constintiMin,constintiMax);〃位于CSystem类中〃MakeSpriteName:将前面的字符串与后面的数字整合成一个字符串。//参数szPrev:ー个非空字符串,最长不能超过20个英文字符。名字前面的字符。//参数ild:ー个数字/Z返回值:返回一个字符串,比如传入("xxx",2),则返回"xxx2"staticchar*MakeSpriteName(constchar*szPrev,constintiId);//GetName/Z返回值:返回精灵名字constchar*GetName();Vector介绍vector是ー个容器,它能够像容器ー样存放各种类型的对象,简单地说,vector是ー个能够存放任意类型的动态数组,能够增加和压缩数据。为了可以使用vector,必须在你的头文件中包含下面的代码:#include<vector>vector属于std命名域的,因此需要通过命名限定,即加入代码:usingstd::vector;如:vector<int>vlnts:定义了一个类型为int的vector变量vlnts其常用的方法有(v表示vector):v.push_back(elem)在尾部加入一个数据。v.size()返回容器中实际数据的个数。Step3 思路思路比较清晰。以rockfishTemplate为模板,调用CSystem::CloneSprite复制多条鱼,然后通过CSystem::RandomRange来随机得到纵向坐标,最后利用SetSpritePositionY来为复制出来的鱼设置各自的Y坐标。复制出来的鱼我们利用vector来统一管理,即每复制出一条鱼,我们利用push_back方法将其压入vector的最后。为了方便根据鱼的名字来定位vector中的CSprite位置,我们自定义ー个FindSpriteByName方法,实现根据提供的精灵名字返回Cprite・。Step4代码在LessonX.h中定义ー个vector变量:vectorくCSprite・〉 rockFishs;思考:rockFishs容纳的变量类型是什么?由于复制操作只需运行一次,不妨就置于Gamelnit中(当然放在构造函数里也可以,但是没有Gamelnit里面清楚),具体如下:Gamelnit中添加:for(inti=0;i<3;i++){char*destFish= :MakeSpriteNameCrockfish*,i);〃生成待拷贝鱼的名字CSprite*tmpFish=newCSprite(destFish): 〃根据产生的名字创建tmpFishtmpFish->CloneSprite("rockfishTemp1"io"); //tmpFish复制rockfishTemplatefloattmpY=CSystem::RandomRange(i*(-10),(i+l)*(-10));〃根据i得到随机Y坐标tmpFish->SetSpritePositionY(tmpY);tmpFish->SetSpriteLinearVe1ocity(15,0);rockFishs.pushback(tmpFish): 〃将tmpFish压入rockFishs中方便管理■思考:如果将上述代码移动到CGameMainO中会怎么样?以上实现了复制三条鱼,并使之以15的速度向右游动,接着添加处理边界碰撞的代码。观察OnSpriteColWorldLimit的参数constchar*szName»发现参数值是碰撞时精灵的名字,而我们存储在vector中的类型为CSprite*,因此需要进行映射,可以通过GetName来获取CSprite的名字,从而完成char*->CSprite*的映射。我们定义ー个FindSpriteByName函数来查找vector中给定名字的精灵,在LessonX.h中添加^ CSprite* FindSpriteByName(constchar*szName);并在LessonX.cpp中完成其定义:CSprite*CGameMain::FindSpriteByName(constchar*szName)for(inti=0;i<rockFishs.size();i++) 〃size()返回vector的大小if(strcmp(szName,rockFishs[i]->GetName0)==0) 〃遍历vector查找与szName具有相同的名称的rockFishs中的精灵returnrockFishs[i];returnNULL;}思考:最后一句的含义是什么?最后在OnSpriteColWorldLimit中添加新生成精灵的碰撞处理:CSprite*tmp;if(strcmp(szName,/zyellowfishz,)==0){if(iColSide==1){mySprite->SetSpriteLinearVelocityX(-10.f);mySprite->SetSpriteFlipX(true);}elseif(iColSide==0){mySprite->SetSpriteLinearVelocityX(10.f);mySprite->SetSpriteFlipX(false);}Jelseif((tmp=FindSpriteByName(szName))!=NULL){ 〃如果找到同szName同名的精灵if(iColSide=1){tmp->SetSpriteLinearVelocityX(-15.f);tm|)->SetS[)ri10FlipX(true);}elseif(iColSide==0){tmp->SetSpriteLinocirVelocityX(15.f);tmp->SetSpriteFlipX(false);))编译运行即可,最后的效果图如下所示:

实验:黄金矿エ完成本实验后,可以实现矿エ抓金子并计分的效果,如下所示:复制金块学习目标学习应用GetScreenLeft,GetScreenRight,GetScreenTop,GetScreenBottom学习应用srand函数。这节我们实现黄金矿エ的金块随机分布,并设置不同金块的大小,为后续的抓取金块做好准备。Stepl:图片资源导入新建一个新的项目,取名为GoldenManDemOo添加如图所示资源到环境中:Step2;Funcode接口介绍//CSystem中定义//GetScreenLeft:获取世界边界之左边X坐标/Z返回值:左边界X坐标staticfloatGetScreenLeft();//CSystem中定义//GetScreenTop:获取世界边界之上边Y坐标//返回值:上边界Y坐标staticfloatGetScreenTop();//CSystem中定义//GetScreenRight:获取世界边界之右边X坐标//返回值:右边界X坐标staticfloatGetScreenRight();//CSystem中定义//GetScreenBottom!获取世界边界之下边Y坐标//返回值:下边界Y坐标staticfloatGetScreenBottom();//CSprite中定义//SetSpriteWidth:设置精灵外形宽度/Z参数fWidth:宽度值,大于〇voidSetSpriteWidth(constfloatfWidth);//CSprite中定义//SetSpriteHeight:设置精灵外形高度/Z参数fHeight:精灵高度值voidSetSpriteHeight(constfloatfHeight);Step3:思路阐述要实现金块的随机分布,首先需要利用模板复制一定数量的金块,然后将他们随机布置在窗口中,可以通过SetSpritePosition来设置金块的横纵坐标。接下来要面临的问题就是,如何获取窗口中不同位置的坐标。利用GetScreenLeft,GetScreenRight,GetScreenBottom,GetScreenTop来获取窗口的范围,然后使用RandomRange在该范围中随机一个新的坐标。对于金块的大小设置,可以利用SetSpriteHeight和SetSpriteWidth来实现。Step4:代码及注释首先正义类成员变量,定义如下变量:intm_fGo1dBornMinX;intm_fGoldBornMaxX;intm_fGoldBornMinY;intm_fGoldBornMaxY;intm_iGoldCount;vector<CSprite*>goldsi〃管理金块,原理同海底世界中的rockFishs类似在WinMain的初始化修改添加如下代码:dSetWindowTitle(“黄金矿匚”);在CGameMain的构造函数中加入:m_iGameState = 1;思考:还记得这句话的作用是什么吗金子的生成属于游戏初始化工作,在Gamelnil中完成最为合适先设定初始化参数m_iGoldCount=20: 〃金子初始数量mfGoldBornMinX=CSystem::GetScreenLeft()+5;〃金子左边界m_fGo1dBornMaxX=CSystem::GetScreenRight()-5;〃金子右边界mfGoldBornMinY=CSystem::GetScreenTop()+20;〃金子上边界m_fGo1dBornMaxY=CSystem::GetScreenBottom()-5;〃金子下边界利用循环生成20个金子,以goldTemplate为模板intiLoop=0J〃循环变量控制intiSize= 4,iPosX=0,iPosY=0;//iSize表示金块大小的变量for(iLoop=0;iLoop<miGoldCount;iLoop++)if(iLoop<10) //生成10个小金块,大小为4TOC\o"1-5"\h\ziSize= 4;)elseif(iLoop>=10&&iLoop<16) 〃生成6个中金块,大小为6iSize= 6;}else 〃生成4个大金块,大小为8■iSize= 8;)〃初始化金子精灵实例char*tmpName;tmpName=CSystem::MakeSpriteName(^GoldBlock^,iLoop);7生成金块名’エCSprite*tmpSprite=newCSprite(tmpName);tmpSprite->CloneSprite(*goldTemplate");tmpSprite->SetSpriteWidth(iSize)1 〃设置金块的宽度tmpSprite->SetSpriteHeight(iSize): 〃设置金块的高度〃随机的获取金子的X坐标和Y坐标iPosX=CSystem::RemdomRange(m_fGoldBornMinX,m_fGoldBornMaxX);iPosY=CSystem::RandomRange(m_fGoldBornMinY,m_fGoldBornMaxY);〃设置金块精灵的位置tmpSprite->SetSpritePosition(iPosX,iPosY);golds.push_back(tmpSprite);〃将金块压入goldsvector中集中管理编译运行,可得到如下所示的效果:练习:.自定义金子的大小和数量:.不利用srand((unsigned)time(NULL));函数会是什么效果(效果一样)旋转钩子学习目标学习应用SetSpriteRotation函数学会利用屏幕刷新时间fDeltaTime来实现ー些复杂的操作。这节我们实现黄金矿エ的钩子180度来回转动,这样矿エ就可以控制抓取范围了,也为后面的抓取金子做好准备。Steph图片资源导入点击添加一个新的动画画@画包画!画I画添加Go1derManAnimation1,Go1derManAnimation2,Go1derManAnimation3到环境中,添加完后效果如图:v动!ffii精ズ(Sprites)

将Go1derManAnimation2拖进屏幕,并取名为GoldMan»如下图所示:名字 goldMan同理,将静态精灵中的钩子,拖进屏幕,按初始界面排放。并在右侧的“编辑”中设置名称如下:钩子goldhool-|名字goldhool-|名字Step2:Funcode接口介绍//CSprite中定义//SetSpriteRotation:设置精灵的旋转角度//参数fRot:旋转角度,范围〇ー360voidSetSpriteRotation(constfloatfRot);Step3:思路阐述实现钩子的180度来回摆动,其实就是不断地设置钩子与地面的夹角从0—180度不断地变化,可以利用SetSpriteRotation来进行设置。为了使钩子以恒定的速度来回摆动,我们可以自定义ー个初始化速度speed,然后利用游戏屏幕刷新的时间fTimeDelta来实现,即speed*fTimeDelta即为当前钩子所在的角度位置。Step4:代码及注释首先定义类成员变量,定义如下变量:floatmfHookRotation;〃钩子同地面的夹角intm_iHookRotToLeft;〃钩了一摆动的方向:CSprite*goldHook;〃钩子对应的精灵在CGameMain的构造函数中创建钩子实例:goldHook=newCSprite("goldhook"); 〃创建钩子实例在Gamelnit中初始化参数(当然也可以放到构造函数中,但放到Gamelnit中显得更清晰)m_iHookRotToLeft=1: 〃钩子初始化方向为一m_fHookRotation=0.f; 〃初始化的夹角为〇由于钩子摆动在游戏过程中一直进行,所以应该将摆动的代码置于GameRun中,如下:constfloatfRotateSpeed=45.f; /Z摇摆速度,单位度/秒floatfThisRotate=fRotateSpeed*fDeltaTime;//本次旋转的角度if(miHookRotToLeft){ 〃向左转,度数不断变大m_fHookRotation+=fThisRotate;if(m_fHookRotation>=180.f){〃大于180,置为向右转,即〇m_fHookRotation = 180.f;TOC\o"1-5"\h\zm_iHookRotToLeft= 0;else{〃向右转,度数不断变小mfHookRotation-=fThisRotate; 〃小于〇,置为向左转,即1if(mfHookRotation<=0.f){m_fHookRotation= 0. f;m_iHookRotToLeft= 1;})goldHook->SetSpriteRotation(m_fHookRotation); 〃设置钩子的当前角度编译运行,可得到如下所示的效果:练习:1.使钩子从左往右进行旋转释放绳索学习目标学习应用SetSpriteLinearVelocityPolar,AnimateSpriteP1ayAnimation函数学习GetSpriteLinkPointPosX,GetSpriteLinkPointPosY,Drawline函数学会利用成员变量来控制游戏的运行状态。这节我们实现黄金矿エ在某一角度放下绳索的操作,为之后的真正抓取金子做好准备Stepl:图片资源编辑打开funcode,单击地图上的矿エ精灵图像,在显示框的上面五个选择左数第二个‘’编按如图所示依次单击这位置显示0,保存即可。同上,点击地图上的钩子精灵,选择“编辑此精灵的链接点”,依次单击这个位置显示0«最后保存即可。

Step2:Funcode接口介绍//CSprite中定义//SetSpriteLinearVelocityPolar:按角度朝向设置精灵移动速度//参数fSpeed:移动速度//参数fPolar:角度朝向voidSetSpriteLinearVelocityPolar(constfloatfSpeed,constfloatfPolar);//CAnimateSprite中定义//AnimateSpritePlayAnimation:动画精灵播放动画//参数szAnim:动画名字//参数bRestore:播放完毕后是否恢复当前动画/Z返回值:是否播放成功bool AnimateSpritePlayAnimation(constchar*szAnim,constboolbRestore);//CSprite中定义//GetSpriteLinkPointPosX:获取精灵链接点X坐标。链接点是依附于精灵的ー个坐标点,可以在编辑器里增加或者删除/Z参数ild:链接点序号,第一个为1,后面依次递加floatGetSpriteLinkPointPosX(constintild);//CSprite中定义//GetSpriteLinkPointPosY:获取精灵链接点Y坐标。链接点是依附于精灵的ー个坐标点,可以在编辑器里增加或者删除/Z参数ild:链接点序号,第一个为1,后面依次递加floatGetSpriteLinkPointPosY(constintild);//CSystem中定义//DrawLine:在两点之间画一条线/Z参数fStartX:起始坐标X/Z参数fStartY:起始坐标Y/Z参数fEndX:终点坐标X/Z参数fEndY:终点坐标Y/Z参数fLineWidth:线的粗细,大于等于1/Z参数iLayer:改线所在的层,与编辑器里设置的精灵的层级是同一个概念。范围。-3I〇/Z参数iRed,iGreen,iBlue:红绿蓝三原色的颜色值,范围〇ー255/Z参数iAlpha:线的透明度,范围0-255.0为全透明,255为不透明staticvoid DrawLine(constfloatfStartX,constfloatfStartY,constfloatfEndX,constfloatfEndY,constfloatfLineWidth,constintiLayer,constintiRed,constintiGreen,constintiBlue,constintiAlpha);

Step3:思路阐述利用SetSpriteLinearVelocityPolar即可实现钩子在某一角度放线,另外可以通过AnimateSpritePlayAnimation来播放放线时矿エ的动作,所以放线问题不大。但要注意三个问题:何时放线,放线的时候钩子停止旋转,在钩子和矿エ之间画一条线表示绳索。下面来解决这两个问题:何时放线:我们不妨利用键盘上的方向键中的!来控制放线,在OnKeyDown添加响应释放绳索的操作,这和海底世界中的WSAD控制鱼的方向原理类似;放线的时候钩子停止旋转:与海底世界中鼠标控制鱼游动一样,可以添加一个类成员变量,当该变量为〇时,钩子旋转;当释放绳索时,置其为1,这样钩子就不会旋转了。绳索的展现:在step1中已经设置了链接点,其中矿エ的〇和钩子的〇已经设置好,通过GetSpriteLinkPointPosX和GetSpriteLinkPointPosY获取这两个链接的位置,然后利用DrawLine函数在两点之间画线即可。Step4:代码及注释在LessonX.h中定义类成员变量:int miHookState: 〃钩子状态,〇表示钩子旋转,1表示释放绳索,钩子不转float mfHookSpeedi 〃钩子释放的速度CAnimateSprite*goldMan; 〃矿エ对应的Sprite思考:为什么要用CAnimateSprite来表示矿エ?先在CGameMainO中创建矿エ精灵: goldMan=newCAnimateSprite(zzgoldMan/z);然后在LessonX.cpp的Gamelnit中完成钩子的初始化:miHookState二0;〃初始情况下使钩子旋转m_fHookSpeed=15.f;在CGameMain中声明OnKeyDown方法:void OnKeyDown(constintiKey,constboolbAltPress,constboolbShiftPress,constboolbCtrlPress);在LessonX.cpp中定义该函数,并添加响应I按键的代码:voidCGameMain::OnKeyDown(constintiKey,constboolbAltPress,constboolbShiftPress,constboolbCtrlPress){if(KEYDOWN==iKey&&miHookState==0){ 〃按下l并且游戏状态为“1”miHookState=1: 〃置游戏状态为1,可用于控制钩子是否摆动//以当前朝向给钩子ー个向前的速度goldHook->SetSpriteLineurV。locityPolar(inIHookSpeed,m_fHookRotation);//播放挖金者的动作(ー个胳膊往下压的动作)go1dMan->AnimateSpriteP1ayAnimation(Z/Go1derManAnimation,0);//0表示播放一次,这里胳晒往卜压就是ー次在GameRun中,修改钩子摆动的代码,以控制摆动状态,即加入了if判断。if(m_iHookState==0){ 〃当m_iHookState==0时钩子オ摆动constfloatfRotateSpeed=45.f; /Z摇摆速度,单位度/秒floatfThisRotate=fRotateSpeed*fDeltaTime;//本次旋转的角度if(m_iHookRotToLeft){ 〃向左转,度数不断变大mfHookRotation+=fThisRotate;if(m_fHookRotation>=180.f){〃大于180,置为向右转,即〇m_fHookRotation = 180.f;m_iHookRotToLeft = 0;})else{ 〃向右转,度数不断变小m_fHookRotation-=fThisRotate; 〃小于〇,置为向左转,即1if(m_fHookRotation<=0.f){m_fHookRotation = 0.f;m_iHookRotToLeft = 1;})goldHook->SetSpriteRotation(m_fHookRotation); 〃设置钩子的当前角度)最后,还是在GameRun中,添加画线连接的操作:elseif(miHookState=1){ 〃当钩子伸出后オ需要画线//首先,从矿丄精乂エ获取・个缆绳链接点作为绳子的起始点(该链接点在编辑器里编辑好)floatfStartX=go1dMan->GetSi-iteLinkPointPosX(1);floatfStartY=goldMan->GetSpriteLinkPointPosY(1);/Z绳子终点在钩子精灵上获取(该链接点在编辑器里编辑好)floatfEndX=goldHook->GetSp

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论