原文链接:http://www.raywenderlich.com/6888/how-to-make-a-multi-directional-scrolling-shooter-part-2 这里使用cocos2dx做了移植。

开发环境为vs2012和cocos2dx 2.0

文中的代码根据个人习惯稍有修改。

在第一部分的内容中,我们创建了一个全新的Cocos2Dx 2.0项目,将瓦片地图添加到游戏中,并添加了一个坦克,可以使用加速计来进行操控。

在这部分(也是最后一部分)的内容中,我们将让坦克可以发射炮弹,同时会添加敌军坦克,添加游戏的赢/输机制,等等。

接下来的内容将从第一部分已完成的项目开始,可以从这里(http://download.csdn.net/detail/hany3000/5204725 )直接下载。

准备好了,还是来制作游戏吧。

炮火连天

现在我们的坦克已经可以四处移动了,但还不能开火!坦克要开火跟女生要买新衣服一样天经地义,所以必须得尽快解决这个问题:)

当然,由于之前使用加速计来控制坦克的移动,这里可以直接使用触摸的方式让坦克开火。不过,为了让游戏变得更有趣一点,我们不仅要在玩家触摸的时候发货,还可以让坦克连续开火!

到Tank.h,在其中做出以下修改:

//second part
 cocos2d::CCPoint shootVector;//射击的方向
 double timeSinceLastShot;//从上次射击到现在所经过的时间
 cocos2d::CCSprite *turret;//坦克炮塔

bool shooting;
 void shootToward(cocos2d::CCPoint stPosition);
 void shootNow();

在上面的代码中,我们添加了一个实例变量shootVector用于保存射击的方向,变量timeSinceLastShot用于保存从上次射击到现在所经过的时间。还添加了一个turret变量来保存添加到坦克顶部的新精灵对象-坦克的炮塔!

切换到Tank.cpp,并对代码做出以下调整:

在文件的顶部添加:

#include "SimpleAudioEngine.h"

在initWithLayer 方法中添加以下代码:

do
 {
 CC_BREAK_IF(!CCSprite::initWithSpriteFrameName("tank1.png"));

CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("tank%d.png",type)->getCString());

_layer=layer;
 _type = type;
 moving=false;
 this->hp=hp;
 this->scheduleUpdateWithPriority(-1);
 
 //创建坦克炮塔
 CCString turretName=CCString::createWithFormat("tank%d_turret.png",type)->getCString();
 turret = CCSprite::createWithSpriteFrameName(turretName.getCString());
 turret->setAnchorPoint(ccp(0.5,0.25));
 turret->setPosition(ccp(this->getContentSize().width/2,this->getContentSize().height/2));
 this->addChild(turret);

}while(0);
 return;

在以上代码中,我们创建了一个新的精灵对象代表坦克炮塔,并将其添加为坦克的子节点。这样当我们移动坦克精灵的时候,炮塔也会随之移动。

请注意放置炮塔的方式:

首先将锚点的位置设置在靠近炮塔的基座。为什么这样做呢?因为锚点的位置就是旋转的中心点,而我们想要让炮塔沿着基座旋转,就必须将锚点设置在靠近基座。

接着我们将炮塔精灵对象的位置设置在坦克的中心。由于炮塔精灵是坦克的子节点,其位置是相对坦克的左下角的。这样我们就把锚点(炮塔的基座)连接在坦克的中心点上。

接下来在文件的底部添加一个新的方法:

void Tank::shootToward(CCPoint stPosition)
{
 CCPoint offset = ccpSub(stPosition,this->getPosition());
 float MIN_OFFSET = 10;
 if(ccpLength(offset)<MIN_OFFSET) return;

shootVector = ccpNormalize(offset);

shooting = true;
}

当玩家触摸屏幕的时候,就会调用该方法。这里需要检查触摸点到目标位置的距离不小于10个点(如果太近,则很难判断射击的方向)。接着我们将向量规范化(也即把向量的长度设置为1),从而得到一个射击的方向向量,并将其保存在shootVector变量中,以便后续使用。

接下来添加实际射击的方法如下:

void Tank::shootNow()
{
 //1
 float angle = ccpToAngle(shootVector);
 this->turret->setRotation(-1*CC_RADIANS_TO_DEGREES(angle));
 
 //2
 float mapMax=MAX(this->_layer->tileMapWidth(),this->_layer->tileMapHeight());
 CCPoint actualVector = ccpMult(shootVector,mapMax);
 //3
 float POINTS_PER_SECOND = 300;
 float duration  = mapMax/POINTS_PER_SECOND;

//4
 CCString shootSound = CCString::createWithFormat("tank1Shoot.wav",this->_type)->getCString();
 CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect(shootSound.getCString());

//5
 CCString bulletName=CCString::createWithFormat("tank%d_bullet.png",_type)->getCString();
 CCSprite* bullet=CCSprite::createWithSpriteFrameName(bulletName.getCString());

bullet->setTag(_type);
 bullet->setPosition(ccpAdd(this->getPosition(),ccpMult(shootVector,turret->getContentSize().height)));

CCMoveBy *move = CCMoveBy::create(duration,actualVector);
 CCCallFuncN *call=CCCallFuncN::create(this,callfuncN_selector(Tank::shootEnd));

bullet->runAction(CCSequence::create(move,call,NULL));
 _layer->_batchNode->addChild(bullet);
}

让我们来解释下其中的代码(按照注释顺序):

1.首先我们将炮塔选中到面朝射击的方向。这里使用一个简单的辅助函数ccpToAngle,可以将向量转换成以弧度为单位的向量角度。急着将其转换成Cocos2D中使用的角度,然后乘以——1,因为在Cocos2D中使用顺时针旋转。同时需要加上90,因为炮塔的美术素材是朝上的(而不是朝右)。

2.接着我们计算出炮弹要射击的距离。这里我们获得瓦片地图宽度或高度的最大值,并乘以射击的方向向量;

3.再接下来我们需要计算出炮弹要达到指定地点所需的时间。这一点很简单,只需使用向量长度(瓦片地图宽度或高度中更大的数值)除以每秒的运动速度即可

4.添加音效

5.最后我们创建了一个新的炮弹精灵,并让其执行某个动作(在动作完成后消失),并将其添加到层的精灵表单中。

接下来对Tank.cpp做出以下修改:

添加以下新的方法:

bool Tank::shouldShoot()
{
 if(!this->shooting) return false;

double SECS_BETWEEN_SHOTS = 0.25;
 if(timeSinceLastShot > SECS_BETWEEN_SHOTS)
 {
  timeSinceLastShot = 0;
  return true;
 }
 else
 {
  return false;
 }
}

void Tank::updateShoot(float dt)
{
 timeSinceLastShot += dt;
 if(this->shouldShoot())
 {
  this->shootNow();
 }
}然后修改update方法如下:

void Tank::update(float dt)
{
 this->updateMove(dt);
 updateShoot(dt);
}

通过以上代码,可以让坦克连续射击。每一次更新我们都会调用updateShoot方法。如果从上次射击到现在的时间超过了0.25秒,则调用shootNow方法。

好了,Tank.cpp已经完成。到HelloWorldLayer.cpp,并使用以下内容替代ccTouchesBegan和ccTouchesMoved:

CCSetIterator it = pTouches->begin();

CCTouch *touch= (CCTouch*)(*it);

CCPoint mapLocation = _tileMap->convertTouchToNodeSpace(touch);

this->tank->shooting = true;
 this->tank->shootToward(mapLocation);
  tank->moving=true;
  tank->moveToward(mapLocation);

通过以上的方法,我们使用触碰来进行射击,而非移动坦克。

编译运行游戏,可以触摸屏幕连续射击了!

当然,这里采用的射击方式并非是最佳的,因为我们在连续分配炮弹,而在ios中这样是非常耗费内存的。一个更好的方式是预先分配一个炮弹数组,并在需要发射炮弹时重用之前的旧炮弹。

添加敌军坦克

任何一个坦克对战游戏都需要有敌军坦克, 打开HelloWorldLayer.h,然后创建一个数组用于保存敌军坦克:

CCArray *enemyTanks;

然后打开HelloWorldLayer.cpp,并在init方法的地步添加以下代码,以产生一些敌军坦克:

enemyTanks = CCArray::create();
  this->enemyTanks->retain();
  //产生一些敌对坦克
  int  NUM_ENEMY_TANKS = 50;
  for(int i=0;i<NUM_ENEMY_TANKS;i++)
  {
   RandomTank* enemy = new RandomTank();
   enemy->initWithLayer(this,2,2);
   CCPoint randSpot;
   bool inWall = true;
   while(inWall)
   {
    randSpot.x=CCRANDOM_0_1()*this->tileMapWidth();
    randSpot.y=CCRANDOM_0_1()*this->tileMapHeight();
    inWall = this->isWallAtPosition(randSpot);
   }
   enemy->setPosition(randSpot);
   
   this->_batchNode->addChild(enemy);
   enemyTanks->addObject(enemy);
  }
  //
  explosion = CCParticleSystemQuad::create("explosion.plist");
  explosion->stopSystem();
  _tileMap->addChild(explosion,1);

explosion2 = CCParticleSystemQuad::create("explosion2.plist");
  explosion2->stopSystem();
  _tileMap->addChild(explosion2,1);

exit = CCSprite::createWithSpriteFrameName("exit.png");
  CCPoint exitTileCoord = ccp(98,98);
  CCPoint exitTilePos = positionForTileCoord(exitTileCoord);
  exit->setPosition(exitTilePos);
  _batchNode->addChild(exit);

this->setScale(0.5);

以上代码不难理解。我们在一些随机点创建了一批坦克(只要不是在水中)。

编译运行,可以看到敌军坦克遍布地图!为了方便坦克英雄识别,这里将敌军坦克都标识为红色!

敌军凶猛!

如果这些敌军坦克只是静坐修禅,当然最好不过!不过这样游戏也少了很多乐趣!这里将从Tank类派生一个子类RandomTank,并覆盖其中的一些方法。

在Xcode中使用iOS\Cocoa Touch\Objective-C class模板创建一个新的文件,将其命名为RandomTank,并将subclass of设置为Tank。打开RandomTank.h,并使用以下的代码替代其中的内容:

#pragma once
#include "tank.h"
#include "cocos2d.h"

class HelloWorld;

class RandomTank :
 public Tank
{
public:
 RandomTank(void);
 ~RandomTank(void);
 CREATE_FUNC(RandomTank);

double timeForNextShot;
 void  initWithLayer(HelloWorld* theLayer,int theType,int theHp);
 void move(float dt);
 bool shouldShoot();
 void calcNextMove();
 bool clearPathFromTileCoord(cocos2d::CCPoint start,cocos2d::CCPoint end);
};

这里添加了一个实例变量,用于记录到下一次设计前要等候多少秒。

切换到RandomTank.cpp,并使用以下代码替代其中的内容:

#import "

#include "RandomTank.h"
#include "HelloWorldScene.h"
 
using namespace cocos2d;

RandomTank::RandomTank(void)
{
}

RandomTank::~RandomTank(void)
{
}

void RandomTank::initWithLayer(HelloWorld* theLayer,int theType,int theHp)
{
 Tank::initWithLayer(theLayer,theType,theHp);
 this->schedule(cocos2d::SEL_SCHEDULE(&RandomTank::move),0.5f);
}

bool RandomTank::shouldShoot()
{
 if(ccpDistance(this->getPosition(),this->_layer->tank->getPosition())> 600)
  return false;
 if(timeSinceLastShot >timeForNextShot)
 {
  timeSinceLastShot=0;
  timeForNextShot = (CCRANDOM_0_1()*3)+1;

shootToward(_layer->tank->getPosition());
  return true;
 }
 else return false;
}
void RandomTank::calcNextMove()
{
 bool moveOK=false;
 CCPoint start = _layer->tileCoordForPosition(this->getPosition());
 CCPoint end;
 while(!moveOK)
 {
  end = start;
  end.x +=CCRANDOM_MINUS1_1()*((rand()%10)+3);
  end.y +=CCRANDOM_MINUS1_1()*((rand()%10)+3);
  moveOK=clearPathFromTileCoord(start,end);
 }
 CCPoint moveToward = _layer->positionForTileCoord(end);
 this->moving = true;
 this->moveToward(moveToward);

}
void RandomTank::move(float dt)
{
 if(this->moving && rand()%3 !=0) return;

this->calcNextMove();
}

以上定时了一个移动方法,每半秒调用一次。

当进行射击时,我们会首先确保敌军坦克离坦克英雄足够近,否则如果敌军坦克在很远的地方就开炮,会让游戏难度大大提升。

接下来我们计算出下一次射击的随机时间,大概在1-4秒之间。如果达到该时间,会更新坦克的目标,并继续。

在HelloWorldLayer.cpp中添加以下代码:

#include"RandomTank.h"

然后在init方法中修改创建正常坦克的代码,如下:

RandomTank* enemy = new RandomTank();
   enemy->initWithLayer(this,2,2);

编译运行游戏,当坦克英雄距离敌军坦克一定距离的时候,敌军就会开炮了!

让敌军坦克动起来

现在虽然敌军坦克已经开始射击了,但还需要让它们四处动一动。

为了让游戏尽可能简化,这里采取的策略是:

1.选择一个临近的随机点

2.确保该路径上没有障碍物,如果是,则让坦克朝该点移动

3.如果不是,则返回第一步

这里唯一需要考虑的是第2步!指定起始点和终点的坐标,我们如何走过坦克需要移动的瓦片,并确保不会遇上障碍物?

幸运的是,这个问题已被解决了,可参考James McNeil的博客(http://playtechs.blogspot.com/2007/03/raytracing-on-grid.html),这里我们直接使用他给出的方。

切换到RandomTank.m,并使用以下代码替代calcNextMove方法:

// From http://playtechs.blogspot.com/2007/03/raytracing-on-grid.html

bool RandomTank::clearPathFromTileCoord(cocos2d::CCPoint start,cocos2d::CCPoint end)
{
 int dx = abs(end.x - start.x);

int dy = abs(end.y - start.y);

int x = start.x;

int y = start.y;

int n = 1 + dx +dy;

int x_inc = (end.x>start.x) ? 1: -1;

int y_inc = (end.y>start.y) ? 1: -1;

int error = dx - dy;

dx *=2;

dy *=2;

for(;n>0; --n){

if (_layer->isWallAtTileCoord(ccp(x,y))) return false;

if(error >0){

x += x_inc;

error -= dy;

}

else{

y += y_inc;

error += dx;

}

}

return true;

}

void RandomTank::calcNextMove()
{
 bool moveOK=false;
 CCPoint start = _layer->tileCoordForPosition(this->getPosition());
 CCPoint end;
 while(!moveOK)
 {
  end = start;
  end.x +=CCRANDOM_MINUS1_1()*((rand()%10)+3);
  end.y +=CCRANDOM_MINUS1_1()*((rand()%10)+3);
  moveOK=clearPathFromTileCoord(start,end);
 }
 CCPoint moveToward = _layer->positionForTileCoord(end);
 this->moving = true;
 this->moveToward(moveToward);

}

不要担心上面第一个方法的工作原理(如果感兴趣可以仔细看看那篇博客),只需要知道它可以检查在起点和终点之间是否存在障碍,如果是则返回FALSE。

而在calcNextMove方法中,我们使用了上面的算法。

编译运行,可以看到敌军坦克开始动起来!

碰撞,爆炸和出口

现在我们有敌人可以打,有炮弹可以发射,还需要的就是刺激的爆炸效果,还有就是让坦克英雄取得胜利的出口!

在HelloWorldLayer.h中对代码做出以下修改:

typedef enum
 {
  kEndReasonWin,
  kEndReasonLose

}EndReason;

添加以下几个实例变量;

cocos2d::CCParticleSystemQuad* explosion;
 cocos2d::CCParticleSystemQuad* explosion2;
 bool gameOver;
 cocos2d::CCSprite* exit;

void restartTapped(CCObject* pSender);
 void endScene(EndReason endReason);

接下来切换到HelloWorldLayer.mcpp并在init方法的底部添加以下代码:

explosion = CCParticleSystemQuad::create("explosion.plist");
  explosion->stopSystem();
  _tileMap->addChild(explosion,1);

explosion2 = CCParticleSystemQuad::create("explosion2.plist");
  explosion2->stopSystem();
  _tileMap->addChild(explosion2,1);

exit = CCSprite::createWithSpriteFrameName("exit.png");
  CCPoint exitTileCoord = ccp(98,98);
  CCPoint exitTilePos = positionForTileCoord(exitTileCoord);
  exit->setPosition(exitTilePos);
  _batchNode->addChild(exit);

this->setScale(0.5);

在以上代码中,我们使用Cocos2Dx内置的粒子系统创建了两种不同类型的爆炸效果,并将其添加为瓦片地图的子节点,但首先需要先将其关闭。当需要使用的时候,会把它们移动到需要的地方,并使用resetSystem来启动。

接着我们在地图的右下角添加了一个出口。一旦坦克到达这一点,玩家就赢得了战斗!

注意到这里把层的比例设置为0.5,因为我们希望可以看到地图的更多内容。

现在在update方法的前面添加这些新的方法:

void HelloWorld::restartTapped(CCObject* pSender)
{
 CCDirector::sharedDirector()->replaceScene(CCTransitionFlipX::create(0.5,HelloWorld::scene()));
}
void HelloWorld::endScene(EndReason endReason)
{
 if(gameOver) return;

gameOver = true;
 CCSize winSize=CCDirector::sharedDirector()->getWinSize();

CCString message;
 if(endReason == kEndReasonWin)
  message = "you win";
 else if(endReason == kEndReasonLose)
  message="you lose";

CCLabelBMFont *label;//if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad
 label = CCLabelBMFont::create(message.getCString(),"TanksFont.fnt");
 label->setScale(0.1);
 label->setPosition(ccp(winSize.width/2,winSize.height*0.7));
 this->addChild(label);

CCLabelBMFont *restartLabel;
 restartLabel = CCLabelBMFont::create("Restart","Tanksfont.fnt");

CCMenuItemLabel *restartItem = CCMenuItemLabel::create(restartLabel,this,menu_selector(HelloWorld::restartTapped));
 restartItem->setScale(0.1);
 restartItem->setPosition(ccp(winSize.width/2,winSize.height*0.3));

CCMenu* menu = CCMenu::createWithItems(restartItem,NULL);
 menu->setPosition(CCPointZero);
 this->addChild(menu);

restartItem->runAction(CCScaleTo::create(0.5,4.0));
 label->runAction(CCScaleTo::create(0.5,4.0));

}

以上方法我曾在多个原型游戏中使用。如果看过系列的其它博文应该知道,其作用就是重新启动游戏,这里就不再解释这些了。如果觉得看不太明白,可以先从系列的开始看起。

接下来在update方法的开始添加以下代码:

if(this->gameOver) return;

//2
 if( this->exit->boundingBox().intersectsRect(this->tank->boundingBox()))
 {
  this->endScene(kEndReasonWin);
 }

//3
 CCArray*  childrenToRemove   = CCArray::create();
 CCArray* projectilesToDelete = CCArray::create();

cocos2d::CCArray * childrens=_batchNode->getChildren();

CCObject*pObject=NULL;

CCARRAY_FOREACH(childrens,pObject)
 {
  CCSprite* sprite=(CCSprite*)pObject;
  //CCArray* monstersToDelete=CCArray::create();
  if(sprite->getTag() != 0)//bullet
  {
   if(this->isWallAtPosition(sprite->getPosition()))
    {
     projectilesToDelete->addObject(sprite);
           continue;
       }
  }
  if(sprite->getTag() == 1)//hero bullet
  {
   for(int j=enemyTanks->count()-1;j>=0;j--)
   {
    Tank* enemy = (Tank*)enemyTanks->objectAtIndex(j);

if(sprite->boundingBox().intersectsRect(enemy->boundingBox()))
    {
     childrenToRemove->addObject(sprite);
     enemy->hp --;
     if(enemy->hp <=0)
     {
      CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("explode3.wav");
      explosion->setPosition(enemy->getPosition());
      explosion->resetSystem();
      enemyTanks->removeObject(enemy);
      childrenToRemove->addObject(enemy);

}
     else
     {
                       CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("explode2.wav");
      
     }
    }
   }
  }
  if(sprite->getTag() == 2)//enemy bullet
  {
   if(sprite->boundingBox().intersectsRect(tank->boundingBox()))
   {
    childrenToRemove->addObject(sprite);
    tank->hp --;
    if(tank->hp <=0)
    {
     CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("explode2.wav");
     explosion->setPosition(tank->getPosition());
     explosion->resetSystem();
     this->endScene(kEndReasonLose);
    }//dead
    else
    {
     explosion2->setPosition(tank->getPosition());
     explosion2->resetSystem();
     CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("explode1.wav");
    }
   }
  }//tag==2
 }

CCARRAY_FOREACH(childrenToRemove,pObject)
 {
  CCSprite* sprite = (CCSprite*)pObject;
  
  sprite->removeFromParentAndCleanup(true);
 }
 setViewpointCenter(tank->getPosition());

以上就是碰撞检查和游戏机制,这里稍微解释下:

1.开始记录游戏的状态,游戏是否结束。如果游戏已结束则无需做任何事。

2.如果坦克碰到出口,则玩家赢得胜利!

3.开始碰撞检测,有时候有的精灵在碰撞后需要从屏幕中删除(例如,当炮弹碰到坦克或障碍的时候,会被删除)。

4.对炮弹精灵设置标记,从而可以轻松将其识别

5.如果在炮弹的位置有障碍,则移除炮弹。

6.如果炮弹是由坦克英雄发射的,则检查它是否击中了敌军坦克,如果是,则将敌军坦克的HP减少(如果HP<=0则将其销毁)。同时还播放一个音效,以及激活一个爆炸的粒子系统。

7.与之类似,如果是敌军坦克发射的炮弹,则检查是否击中了坦克英雄,并进行相应的操作。当玩家的HP达到0时游戏以失败告终。

最后一步,在accelerometer:didAccelerate,ccTouchesBegan和ccTouchesMoved方法的前面添加以下代码:

if(gameOver) return;

编译运行游戏,现在就可以尽情的坦克大战了!

如何使用cocos2dx 制作一个多向滚屏坦克类射击游戏-第二部分相关推荐

  1. Cocos2d-x 是一个支持多平台的 2D 手机游戏引擎

    编辑本段简介 Cocos2d-x 是一个支持多平台的 2D 手机游戏引擎,使用 C++ 开发,基于OpenGL ES,基于Cocos2d-iphone,支持 WOPhone, iOS 4.1, And ...

  2. PywebIO 轻松制作一个数据大屏,代码只需100行

    今天我给大家分享一个制作数据大屏的工具,非常的好用,100行的Python代码就可以制作出来一个完整的数据大屏,并且代码的逻辑非常容易理解. PywebIO介绍 Python当中的PywebIO模块可 ...

  3. 如何制作一个可视化大屏模板

    今天我们来一篇demo实例来讲述如何用finereport制作一个合格的大屏demo. 按照文章中所言的几种常见排布方式: 以上是几种常见的大屏排布方式,依据各个大屏模板所需要展示的具体信息我们可以自 ...

  4. 使用 Node 开发一个多人对战的射击游戏

    点击上方 前端瓶子君,关注公众号 回复算法,加入前端编程面试算法每日一题群 来源:我系小西几呀 https://juejin.cn/post/6960096410305822751 相信大家都是知道游 ...

  5. Node 开发一个多人对战的射击游戏(实战长文)

    点击上方 程序员成长指北,关注公众号 回复1,加入高级 Node 进阶交流群 来源:我系小西几呀 https://juejin.cn/post/6960096410305822751 相信大家都是知道 ...

  6. 50 行 Python 代码制作一个数据大屏

    今天给大家分享一个制作数据大屏的工具,非常的好用,100行左右的Python代码就可以制作出来一个完整的数据大屏,并且代码的逻辑非常容易理解. PywebIO介绍 Python当中的PywebIO模块 ...

  7. 用树莓派Raspberry Pi 4B制作一个无线投屏器(20200803)

    目录 无线投屏说明 投屏协议及支持软件 Miracast协议(Windows & Android设备) AirPlay协议(macOS & iOS设备) 无线投屏控制 无线投屏说明 请 ...

  8. 准备使用vue制作开始一个的单机挂机放置类小游戏

    因为疫情的原因,工作有点难找,在家呆着无聊打算做点啥,准备开工一个个人小游戏,一方面是热爱游戏,一方面是想做个项目提升一下对vue的熟练度和掌握能力. 想了许久,打算做一个传奇主题的战斗无止境,刚开始 ...

  9. python接水果游戏代码_【Python】python制作一个接水果和金币的小游戏

    开发工具 Python版本:3.6.4 相关模块: pygame模块: 以及一些Python自带的模块. 相关文件 环境搭建 安装Python并添加到环境变量,pip安装需要的相关模块即可. 原理简介 ...

最新文章

  1. java虚拟机栈帧_Java虚拟机,运行时栈帧结构
  2. 解决linux 升级高版本python3.7后yum不能使用的问题
  3. RPC框架(一)RPC简介
  4. java的默认值规则_Java 8:默认方法解析规则
  5. Android 编程下 AlarmManager
  6. plugin since you are using Gradle version 4.6 or above
  7. directshow 旋转_宜昌中心加工机+A:B型号,高速旋转接头加工
  8. TIOBE 6月编程语言排行榜:Python势不可挡
  9. 你那不是拖延症,只是习惯性逃避
  10. 微服务 SpringBoot 通过jdbcTemplate配置Oracle数据源
  11. asmack xmpp应用遇到的问题
  12. 如何使用Tuxera NTFS for Mac将FAT 32U盘转换为NTFS格式
  13. 小猪cms之怎样查询绑定的微网站模板
  14. android-实现一个简单的视频弹幕,Android未来路在何方
  15. AI人工智能简史-人工智能与炼金术
  16. GitHub上发现个菜谱仓库,来看看程序员都吃啥?
  17. 餐饮店如何做活动吸引人
  18. 计算机 库 英文翻译,计算机专业英文翻译
  19. 判断一个整数是否能被7整除或者数中含7
  20. 如何用Typora记笔记? | 附带Markdown基础教程

热门文章

  1. gm220s路由器怎么设置_二级路由器怎么设置_二级路由器设置图解教程-192路由网...
  2. 不懂数据库的码农不是好程序员!
  3. 光耦p621引脚图_TOSHIBA光耦TLP系列的部分光耦的应用
  4. Linux搭建迅搜( Xunsearch )
  5. SAP所有模块用户出口(User Exits) 二
  6. 齐博x1教程:快速增加下拉菜单
  7. epson连接计算机后无法打印,epson打印机无法打印,教您epson打印机无法打印怎么解决...
  8. “对不起,我们公司只招35岁以上的...”
  9. 国家海洋局的超算应用探索
  10. 手机系统暗潮汹涌 Symbian将何去何从?