如何使用cocos2dx 制作一个多向滚屏坦克类射击游戏-第二部分
原文链接: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 制作一个多向滚屏坦克类射击游戏-第二部分相关推荐
- Cocos2d-x 是一个支持多平台的 2D 手机游戏引擎
编辑本段简介 Cocos2d-x 是一个支持多平台的 2D 手机游戏引擎,使用 C++ 开发,基于OpenGL ES,基于Cocos2d-iphone,支持 WOPhone, iOS 4.1, And ...
- PywebIO 轻松制作一个数据大屏,代码只需100行
今天我给大家分享一个制作数据大屏的工具,非常的好用,100行的Python代码就可以制作出来一个完整的数据大屏,并且代码的逻辑非常容易理解. PywebIO介绍 Python当中的PywebIO模块可 ...
- 如何制作一个可视化大屏模板
今天我们来一篇demo实例来讲述如何用finereport制作一个合格的大屏demo. 按照文章中所言的几种常见排布方式: 以上是几种常见的大屏排布方式,依据各个大屏模板所需要展示的具体信息我们可以自 ...
- 使用 Node 开发一个多人对战的射击游戏
点击上方 前端瓶子君,关注公众号 回复算法,加入前端编程面试算法每日一题群 来源:我系小西几呀 https://juejin.cn/post/6960096410305822751 相信大家都是知道游 ...
- Node 开发一个多人对战的射击游戏(实战长文)
点击上方 程序员成长指北,关注公众号 回复1,加入高级 Node 进阶交流群 来源:我系小西几呀 https://juejin.cn/post/6960096410305822751 相信大家都是知道 ...
- 50 行 Python 代码制作一个数据大屏
今天给大家分享一个制作数据大屏的工具,非常的好用,100行左右的Python代码就可以制作出来一个完整的数据大屏,并且代码的逻辑非常容易理解. PywebIO介绍 Python当中的PywebIO模块 ...
- 用树莓派Raspberry Pi 4B制作一个无线投屏器(20200803)
目录 无线投屏说明 投屏协议及支持软件 Miracast协议(Windows & Android设备) AirPlay协议(macOS & iOS设备) 无线投屏控制 无线投屏说明 请 ...
- 准备使用vue制作开始一个的单机挂机放置类小游戏
因为疫情的原因,工作有点难找,在家呆着无聊打算做点啥,准备开工一个个人小游戏,一方面是热爱游戏,一方面是想做个项目提升一下对vue的熟练度和掌握能力. 想了许久,打算做一个传奇主题的战斗无止境,刚开始 ...
- python接水果游戏代码_【Python】python制作一个接水果和金币的小游戏
开发工具 Python版本:3.6.4 相关模块: pygame模块: 以及一些Python自带的模块. 相关文件 环境搭建 安装Python并添加到环境变量,pip安装需要的相关模块即可. 原理简介 ...
最新文章
- java虚拟机栈帧_Java虚拟机,运行时栈帧结构
- 解决linux 升级高版本python3.7后yum不能使用的问题
- RPC框架(一)RPC简介
- java的默认值规则_Java 8:默认方法解析规则
- Android 编程下 AlarmManager
- plugin since you are using Gradle version 4.6 or above
- directshow 旋转_宜昌中心加工机+A:B型号,高速旋转接头加工
- TIOBE 6月编程语言排行榜:Python势不可挡
- 你那不是拖延症,只是习惯性逃避
- 微服务 SpringBoot 通过jdbcTemplate配置Oracle数据源
- asmack xmpp应用遇到的问题
- 如何使用Tuxera NTFS for Mac将FAT 32U盘转换为NTFS格式
- 小猪cms之怎样查询绑定的微网站模板
- android-实现一个简单的视频弹幕,Android未来路在何方
- AI人工智能简史-人工智能与炼金术
- GitHub上发现个菜谱仓库,来看看程序员都吃啥?
- 餐饮店如何做活动吸引人
- 计算机 库 英文翻译,计算机专业英文翻译
- 判断一个整数是否能被7整除或者数中含7
- 如何用Typora记笔记? | 附带Markdown基础教程
热门文章
- gm220s路由器怎么设置_二级路由器怎么设置_二级路由器设置图解教程-192路由网...
- 不懂数据库的码农不是好程序员!
- 光耦p621引脚图_TOSHIBA光耦TLP系列的部分光耦的应用
- Linux搭建迅搜( Xunsearch )
- SAP所有模块用户出口(User Exits) 二
- 齐博x1教程:快速增加下拉菜单
- epson连接计算机后无法打印,epson打印机无法打印,教您epson打印机无法打印怎么解决...
- “对不起,我们公司只招35岁以上的...”
- 国家海洋局的超算应用探索
- 手机系统暗潮汹涌 Symbian将何去何从?