cocos2d-x学习日志(14) --A星寻路算法之45度地图

一、A星搜索

      他就是一种启发性的算法,根据现在到达这个位置的步数及之后的“估计步数”,即f=g+h,f是整个从起点到终点的代价,g是从起点到我们目前位置的步数,h是从目前位置到终点的估计值,注意这里是估计值,所以我们得到解并不一定是最好的解,具体解“好”到什么程度呢?就是要根据h的估计的好坏,因此只是一个较优解。(以上的部分只能说是我对A星算法较为浅的理解,只能算是初探吧,有不足之处欢迎指正,这里只是为了更好的解释我的程序。)

我的程序的流程可以分为如下几步,

  • 有一个open表,有一个close表,open表首先存储我们的起点,然后由此出发。
  • 首先把起点放入close列表,并检测这点周围点的f值(g+h,h我们通过本点到终点的横纵索引差估计而来)。
  • 把剩下的点放入open列表中,并根据f值进行堆排序。
  • 把f值最小的点放入close列表中,
  • 继续上面的循环,继续处理,直到找到终点为止。

当然我们还要根据我们的游戏做一些处理即是处理地图中的碰撞等,我们要让我们的人物绕过碰撞。


二、代码

Astaritem.h

#pragma once
#include "cocos2d.h"
using namespace cocos2d;
class AstarItem:public CCNode
{
public:
    AstarItem(void);
	~AstarItem(void);
	void setpos(int col,int row);
	int getcol(){return id_col;};
	int getrow(){return id_row;};
	void setg(int g);
	int getg(){return id_g;};
	void seth(int h);
	int geth(){return id_h;};
	void setfid(int fid);
	int getfid(){return id_fid;};
	void setf(int f);
	int getf(){return id_f;};
private:
	int id_col;//列
	int id_row;//行
	int id_g;// 实际代价
	int id_h;// 估计代价
	int id_fid;// 父节点id
	int id_f;// 估价函数f = g + h
};

Astaritem.cpp

#include "Astaritem.h"
AstarItem::AstarItem(void)
{
}
AstarItem::~AstarItem(void)
{
}
void AstarItem::setpos(int col,int row)
{
	id_col = col;
	id_row = row;
}
void AstarItem::setg(int g)
{
	id_g = g;
}
void AstarItem::seth(int h)
{
	id_h = h;
}
void AstarItem::setfid(int fid)
{
	id_fid = fid;
}
void AstarItem::setf(int f)
{
	id_f = f;
}

Astar.h

#pragma once
#include "cocos2d.h"
#include "AstarItem.h"
using namespace cocos2d;
class Astar
{
private:
	int curCol, curRow, aimCol, aimRow;
	int AimX, AimY, AimW, AimH; 
	CCTMXTiledMap* map;
    CCArray *open;
    CCArray *close;
    CCArray *path;
public:
	Astar(void);
	~Astar(void);
	int getG(int col,int row,int id);
	int getH(int col,int row);
	void fromopentoclose();
	void removefromopen();
	void getpath();
	void starseach(int fid);
	void resetSort(int last);
	bool checkclose(int col,int row);
	void addtoopen(int col,int row,int id);
	bool checkmap(int col,int row);
	bool checkOpen(int col,int row,int id);
    CCArray *findPath(int curX, int curY, int aimX, int aimY, CCTMXTiledMap* passmap);
};

Astar.cpp

#include "Astar.h"
#include "Astaritem.h"

Astar::Astar(void)
{
}

Astar::~Astar(void)
{
    open->removeAllObjects();
    close->removeAllObjects();
    path->removeAllObjects();
}
CCArray *Astar::findPath(int curX, int curY, int aimX, int aimY, CCTMXTiledMap* passmap)
{
    //参数以及记录路径数组初始化
	curCol = curX;
    curRow = curY;
	aimCol = aimX;
	aimRow = aimY;
	map = passmap;
	path = new CCArray();
	open = new CCArray();
    AstarItem * temp = new AstarItem();
	open->addObject(temp);
	AstarItem * temp1 = new AstarItem();
	temp1->setpos(curCol,curRow);
	temp1->setg(0);
	int ag = getH(curCol,curRow);
	temp1->seth(ag);
	temp1->setfid(0);
	temp1->setf(ag);
	open->addObject(temp1);
	close = new CCArray();
    //遍历寻找路径
	while(open->count() > 1){
	   fromopentoclose();//open和close列表管理
	   int fatherid = close->count() - 1;
	   if(abs(aimCol - ((AstarItem *)close->objectAtIndex(fatherid))->getcol()) <= 1 && abs(aimRow - ((AstarItem *)close->objectAtIndex(fatherid))->getrow()) <= 1){
		   getpath();
		   break;
	   }else{
           //搜索
	       starseach(fatherid);
	   }
	}
	open->removeAllObjects();
	close->removeAllObjects();
    //获得路径
	if(path->count() == 0){
	   return NULL;
	}else{
		if(((AstarItem *)path->lastObject())->getcol() != aimCol || ((AstarItem *)path->lastObject())->getrow() != aimRow){
		   AstarItem * temp = new AstarItem();
	       temp->setpos(aimCol,aimRow);
		   path->addObject(temp);
		}
		return path;
	}
}
void Astar::starseach(int fid)
{
	int col = ((AstarItem *)close->objectAtIndex(fid))->getcol();
	int row = ((AstarItem *)close->objectAtIndex(fid))->getrow();
    //搜索目前点的上下左右四个方向
	int mycol = col;
	int myrow = row - 1;
	if(myrow >= 0 && checkmap(mycol,myrow)){
		if(checkOpen(mycol,myrow,fid) && checkclose(mycol,myrow)){
		   addtoopen(mycol,myrow,fid);
		}
	}
	mycol = col - 1;
	myrow = row;
	if(mycol >= 0 && checkmap(mycol,myrow)){
		if(checkOpen(mycol,myrow,fid) && checkclose(mycol,myrow)){
		   addtoopen(mycol,myrow,fid);
		}
	}
	mycol = col;
	myrow = row + 1;
	if(myrow < map->getMapSize().width && checkmap(mycol,myrow)){
		if(checkOpen(mycol,myrow,fid) && checkclose(mycol,myrow)){
		   addtoopen(mycol,myrow,fid);
		}
	}
	mycol = col + 1;
	myrow = row;
	if(mycol < map->getMapSize().height && checkmap(mycol,myrow)){
		if(checkOpen(mycol,myrow,fid) && checkclose(mycol,myrow)){
		   addtoopen(mycol,myrow,fid);
		}
	}
}
void Astar::addtoopen(int col,int row,int id)
{
    //向open列表中加入点
    AstarItem * temp = new AstarItem();
	temp->setpos(col,row);
	temp->setfid(id);
	int g = getG(col, row, id);
	int h = getH(col, row);
	temp->setg(g);
	temp->seth(h);
	temp->setf(g + h);
	open->addObject(temp);
	resetSort(open->count() - 1);

}
bool Astar::checkclose(int col,int row)
{
    //检查close列表
	for(int i = close->count() - 1;i > 0;i --){
        if(((AstarItem *)close->objectAtIndex(i))->getcol() == col && ((AstarItem *)close->objectAtIndex(i))->getrow() == row){
           return false; 
		}
	}
	return true;
}
bool Astar::checkOpen(int col,int row,int id)
{
    //检查open列表中是否有更小的步长,并排序
	for(int i = open->count() - 1;i > 0;i --){
		if(((AstarItem *)open->objectAtIndex(i))->getcol() == col && ((AstarItem *)open->objectAtIndex(i))->getrow() == row){
		    int tempG = getG(col,row,id);
			if(tempG < ((AstarItem *)open->objectAtIndex(i))->getg()){
			   ((AstarItem *)open->objectAtIndex(i))->setg(tempG);
			   ((AstarItem *)open->objectAtIndex(i))->setfid(id);
			   ((AstarItem *)open->objectAtIndex(i))->setf(tempG + ((AstarItem *)open->objectAtIndex(i))->geth());
			   resetSort(i);
			}
			return false;
		}
	}
	return true;
}
bool Astar::checkmap(int col,int row)
{
    //检查地图中是否有碰撞
   if(abs(aimCol - col) > 1 || abs(aimRow - row) > 1){
	  CCTMXLayer* layer = map->layerNamed("trees2");
      int tilegid = layer->tileGIDAt(ccp(col,row));
	  CCDictionary *tiledic = map->propertiesForGID(tilegid);
    
     
       if (tiledic)
       {
           CCDictionary *properties = map->propertiesForGID(tilegid);
           if (properties)
           {
               const CCString *collision = properties->valueForKey("conflict");
               if (collision && collision->compare("true") == 0)
               {
                   return false;
               }
           }
       }

       
       
//	  CCString *mvalue = (CCString *)tiledic->objectForKey("conflict");
//      int mv = mvalue->intValue();
//	  if(mv == 1){
//	     return false;
//	  } 
   }
   return true;
}
void Astar::getpath()
{
    //从整个close数组中找出路径
	if(path->count() == 0){
		path->addObject(close->objectAtIndex(close->count() - 1));
	}else{
	    path->insertObject(close->objectAtIndex(close->count() - 1),path->count() - 1);
	}
	while(true){
		if(((AstarItem *)path->objectAtIndex(0))->getg() == 0){
		   break;
		}
		path->insertObject(close->objectAtIndex(((AstarItem *)path->objectAtIndex(0))->getfid()),0);
	}
	curCol = aimCol;
	curRow = aimRow;
}
void Astar::fromopentoclose()
{
    //把open列表中的点放到close列表中
	AstarItem * temp = (AstarItem *)open->objectAtIndex(1);
	close->addObject(temp);
	removefromopen();
}
void Astar::removefromopen()
{
	int last = open->count() - 1;
	open->replaceObjectAtIndex(1,open->lastObject(),true);
	open->removeLastObject();
	last = open->count() - 1;
    //堆排序
	int head = 1;
	while((head * 2 + 1) <= last){
	   int child1 = head * 2;
	   int child2 = head * 2 + 1;
	   int childmin = (((AstarItem *)open->objectAtIndex(child1))->getf() < ((AstarItem *)open->objectAtIndex(child2))->getf() ? child1:child2);
	   if(((AstarItem *)open->objectAtIndex(head))->getf() <= ((AstarItem *)open->objectAtIndex(childmin))->getf()){
	      break;
	   }
       //AstarItem * temp = (AstarItem *)open->objectAtIndex(childmin);
	   open->replaceObjectAtIndex(childmin,open->objectAtIndex(head),false);
       //temp->release();
	}
}
void Astar::resetSort(int last)
{
    //根据步长排序,堆排序
	while(last > 1){
	   int half = last/2;
	   if(((AstarItem *)open->objectAtIndex(half))->getf() <= ((AstarItem *)open->objectAtIndex(last))->getf())
		   break;
	   AstarItem * temp = (AstarItem *)open->objectAtIndex(last);
	   open->replaceObjectAtIndex(last,open->objectAtIndex(half),false);
	   open->replaceObjectAtIndex(half,temp,true);
	   temp->release();
	}
}
int Astar::getG(int col,int row,int id)
{
    //获得该点的g函数值
	int fx = ((AstarItem *)close->objectAtIndex(id))->getcol();
	int fy = ((AstarItem *)close->objectAtIndex(id))->getrow();
	int fg = ((AstarItem *)close->objectAtIndex(id))->getg();
	if(fx - col != 0 && fy - row != 0){
	   return fg + 14;
	}else{
	   return fg + 10;
	}
}
int Astar::getH(int col,int row)
{
    //获得该点的h值
	return abs(aimCol - col) * 10 + abs(aimRow - row) * 10;
}

注:checkmap(int col,int row) 方法中,获得该位置地图上的 trees2层的" conflict"属性值,如果是碰撞的不能通过的位置,则返回false,否则返回true.


A星算法的逻辑部分基本完成。任何一个算法要在游戏开发的过程中使用,需要有特定的场景。这里设定一个简单的场景:45度角地图中,主角需要绕过树到达你点击的目标点。

MapScene.h

#ifndef __MAPSCENE_H__
#define __MAPSCENE_H__

#include "cocos2d.h"

#include "SimpleAudioEngine.h"

#include "Astar.h"
#include "AstarItem.h"

using namespace cocos2d;

class MapScene :public CCLayer
{
private:
	CCArray * path;
	int stepindex;
	int smallstepindex;
public:
	int vmove;
	int hmove;
	Astar * myastar;
	CCSprite*	m_tamara;
	virtual bool init(); 
	void update(float dt);
	//void MapScene::update1(ccTime dt);
	static cocos2d::CCScene* scene();
	CCPoint convertto2d(float x,float y);
	virtual void menuCloseCallback(CCObject* pSender);
	virtual void ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent);
	CREATE_FUNC(MapScene);
};

#endif

MapScene.cpp

#include "MapScene.h"
#include "Astar.h"
#include "AstarItem.h"
#include <math.h>

using namespace cocos2d;

enum 
{
	kTagTileMap = 1,
};
CCScene* MapScene::scene()
{
    CCScene *scene = CCScene::create();
    MapScene *layer = MapScene::create();
    scene->addChild(layer);
    return scene;
}

// on "init" you need to initialize your instance
bool MapScene::init()
{
    if ( !CCLayer::init() )
    {
        return false;
    }
    //初始化地图
	CCTMXTiledMap *map = CCTMXTiledMap::create("iso-test-zorder.tmx");
	addChild(map, 0, kTagTileMap);
	CCSize s = map->getContentSize();
	CCSize winSize = CCDirector::sharedDirector()->getWinSize();
	----UXLOG("ContentSize: %f, %f", s.width,s.height);
	map->setPosition(ccp(-s.width/2 + winSize.width/2,0));
	//初始化人物
	m_tamara = CCSprite::create("Icon.png");
	map->addChild(m_tamara, map->getChildren()->count() );
	m_tamara->retain();
	int mapWidth = map->getMapSize().width * map->getTileSize().width;
	int mapHeight = map->getMapSize().height * map->getTileSize().height;
	m_tamara->setPosition(ccp( mapWidth/2,112));
	m_tamara->setAnchorPoint(ccp(0.5f,0));
	setTouchEnabled(true);
	scheduleUpdate();
	vmove = -1;
	hmove = -1;
	stepindex = -1;
	myastar = new Astar();
    return true;
}
//坐标与地图位置的转换
CCPoint MapScene::convertto2d(float x,float y){
	CCTMXTiledMap* map = (CCTMXTiledMap*) getChildByTag(kTagTileMap);
	int mapWidth = map->getMapSize().width * map->getTileSize().width;
	int mapHeight = map->getMapSize().height * map->getTileSize().height;
	double distanse;
	double sin1;
	double sin11;
	double sin22;
	double cos11;
	double cos1;
	int d2x;
	int d2y;
	double mystatic5 = sqrt(5.0);//«Û∏˘∫≈5
	double mystatic = 16 * mystatic5;//–°ÕºøÈ¿‚≥§
	//char mch[256];
	if(x > mapWidth/2){
	   distanse = sqrt((x - mapWidth/2) * (x - mapWidth/2) + (mapHeight - y) * (mapHeight - y));
	   sin1 = (mapHeight - y)/distanse;
	   cos1 = (x - mapWidth/2)/distanse;
	   sin11 = (sin1 * 2 - cos1) / mystatic5;
	   cos11 = (sin1 + cos1 * 2) / mystatic5;
	   d2y = distanse * 5 / 4 * sin11 / mystatic;
	   sin22 = (2 * sin1 + cos1) / mystatic5;
	   d2x = distanse * 5 / 4 * sin22 / mystatic;
	   return ccp(d2x,d2y);
	}else{
	   distanse = sqrt((mapWidth/2 - x) * (mapWidth/2 - x) + (mapHeight - y) * (mapHeight - y));
	   sin1 = (mapHeight - y)/distanse;
	   cos1 = (mapWidth/2 - x)/distanse;
	   sin11 = (sin1 * 2 - cos1) / mystatic5;
	   cos11 = (sin1 + cos1 * 2) / mystatic5;
	   d2x = distanse * 5 / 4 * sin11 / mystatic;
	   //sin22 = 4.0 * cos11 / 5 + 3.0 * sin11 / 5;
	   sin22 = (2 * sin1 + cos1) / mystatic5;
	   d2y = distanse * 5 / 4 * sin22 / mystatic;
	   return ccp(d2x,d2y);
	}
}
void MapScene::update(float dt)
{
	CCPoint herop = m_tamara->getPosition();
	CCPoint mapindex = convertto2d(herop.x,herop.y);
    //根据路径移动
	if(stepindex >= 1){
	   if(smallstepindex == 0){
		   int ncol = ((AstarItem *)path->objectAtIndex(stepindex))->getcol();
		   int nrow = ((AstarItem *)path->objectAtIndex(stepindex))->getrow();
		   int pcol = ((AstarItem *)path->objectAtIndex(stepindex - 1))->getcol();
		   int prow = ((AstarItem *)path->objectAtIndex(stepindex - 1))->getrow();
		   if(pcol == ncol){
			   if(prow > nrow){
			      vmove = 2;
			   }else if(prow < nrow){
                  vmove = 3;
			   }else{
			      vmove = -1;
			   }
		   }else if(prow == nrow){
			   if(pcol > ncol){
			      vmove = 1;
			   }else if(pcol < ncol){
                  vmove = 0;
			   }else{
			      vmove = -1;
			   }
		   }else{
               vmove = -1;
		   }
	   }
	   if(vmove == 0){
	      herop.x += 1;
	      herop.y -= 0.5;
	   }else if(vmove == 1){
	      herop.x -= 1;
	      herop.y += 0.5;
	   }else if(vmove == 2){
	      herop.x += 1;
	      herop.y += 0.5;
	   }else if(vmove == 3){
	      herop.x -= 1;
	      herop.y -= 0.5;
	   }else if(vmove == 4){
	      herop.x += 1;
	   }else if(vmove == 5){
	      herop.x -= 1;
	   }else if(vmove == 6){
	      herop.y += 0.5;
	   }else if(vmove == 7){
	      herop.y -= 0.5;
	   }
	   smallstepindex ++;
	   if(smallstepindex >= 32){
	      smallstepindex = 0;
		  if(stepindex >= path->count() - 1){
		     stepindex = -1;
			 vmove = -1;
		  }else{
		     stepindex ++;
			 vmove = -1;
		  }
	   }
	}
	m_tamara->setPosition(herop);
    //地图随主角移动
	CCTMXTiledMap* map = (CCTMXTiledMap*) getChildByTag(kTagTileMap);
	CCSize s = map->getContentSize();
	int mapWidth = map->getMapSize().width * map->getTileSize().width;
	int mapHeight = map->getMapSize().height * map->getTileSize().height;
	float deltax = herop.x - mapWidth/2;
	float deltay = herop.y - 112;
	CCSize winSize = CCDirector::sharedDirector()->getWinSize();
    map->setPosition(ccp(-s.width/2 + winSize.width/2 - deltax,-deltay));
}
void MapScene::ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent)
{
	CCSetIterator it = pTouches->begin();
    CCTouch* touch = (CCTouch*)(*it);

    CCPoint m_tBeginPos = touch->getLocationInView();    
    m_tBeginPos = CCDirector::sharedDirector()->convertToGL( m_tBeginPos );
    char mch[256];
	CCTMXTiledMap* map = (CCTMXTiledMap*) getChildByTag(kTagTileMap);
	CCPoint mapp = map->getPosition();
    //获得触摸点位置在地图上的索引(行列)
	CCPoint aimmapindex = convertto2d(m_tBeginPos.x - mapp.x,m_tBeginPos.y - mapp.y);
	if(aimmapindex.x < 0 || aimmapindex.y < 0 || aimmapindex.x >= map->getMapSize().width || aimmapindex.y >= map->getMapSize().height)
	{
		return;
	}
	CCPoint herop = m_tamara->getPosition();
	CCPoint mapindex = convertto2d(herop.x,herop.y);
	CCTMXLayer* layer = map->layerNamed("trees2");
	int tilegid = layer->tileGIDAt(ccp(aimmapindex.x,aimmapindex.y));
    if (tilegid)
    {
        CCDictionary *properties = map->propertiesForGID(tilegid);
        if (properties)
        {
            const CCString *collision = properties->valueForKey("conflict");
            if (collision && collision->compare("true") == 0)
            {
                return;
            }
        }
    }
    
    //A星搜索
	path = myastar->findPath(mapindex.x,mapindex.y,aimmapindex.x,aimmapindex.y,map);
	stepindex = 1;
	smallstepindex = 0;
}
void MapScene::menuCloseCallback(CCObject* pSender)
{
    // "close" menu item clicked
    CCDirector::sharedDirector()->end();
}

效果图:



资源下载:http://download.csdn.net/detail/my183100521/6721821

参考书藉:《Cocos2D-X权威指南》


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
### 回答1: cocos2d-x 4. 学习之路 cocos2d-x是一款开源的跨平台游戏引擎,支持多种平台,包括iOS、Android、Windows、Mac OS X等。cocos2d-x 4.是最新版本,相比之前版本,它有很多新特性和改进,如增强的渲染性能、更好的3D支持、更好的物理引擎等。 如果你想学习cocos2d-x 4.,可以从以下几个方面入手: 1. 学习基础知识:了解cocos2d-x的基本概念、架构和工作原理,掌握cocos2d-x的编程语言和开发环境。 2. 学习API:熟悉cocos2d-x的API,包括场景管理、精灵、动画、音频、物理引擎等。 3. 学习示例代码:通过阅读和分析cocos2d-x的示例代码,了解如何使用cocos2d-x开发游戏。 4. 学习实践:通过实践开发小游戏,掌握cocos2d-x的开发流程和技巧,提高自己的编程能力。 总之,学习cocos2d-x 4.需要一定的时间和耐心,但只要你有兴趣和热情,相信你一定能够掌握它。 ### 回答2: cocos2d-x是一个强大的游戏引擎,可用于开发移动和桌面游戏。随着cocos2d-x更新至4.0版本,它的功能得到了大幅升级。如果你想学习cocos2d-x 4.0,以下是一些重要的步骤和建议。 1. 更改代码结构 cocos2d-x 4.0中启用了新的代码结构,旨在更好地实现模块化和解耦。新代码结构包括Core、Renderer、2d等模块,使代码更易于维护和升级。要理解新代码结构,请先阅读cocos2d-x官方文档,并针对各个模块学习和熟悉其API。 2. 学习新功能 cocos2d-x 4.0中引入了许多新功能,例如Shader、Render Queue等。学习新功能是非常必要的,因为它们将改变以前的游戏开发模式。了解这些新功能的实现原理,并在自己的项目中应用它们,有助于提高游戏性能和质量。 3. 学习C++11 cocos2d-x 4.0开始支持C++11标准,这意味着你可以使用C++11的新特性来编写更好的代码。要理解C++11的特性,建议通读一遍C++11的官方标准,并尝试在cocos2d-x项目中使用这些新特性。 4. 实战练习 最后,实战练习是学习任何技能的关键。为了更好地学习cocos2d-x 4.0,建议你尝试开发自己的游戏项目。通过尝试解决实际问题,你能更好地理解cocos2d-x的API,并在实践中掌握游戏开发的技术。 总而言之,学习cocos2d-x 4.0需要掌握新的代码结构、新的功能和C++11标准,并通过实际项目实战练习来加深理解。这需要一定的时间和耐心,但只要你认真学习、实践和不断尝试,必定能够取得成功。 ### 回答3: cocos2d-x 4.0是目前市面上非常流行的开源游戏开发引擎,在游戏开发领域有着较为广泛的应用。然而,学习cocos2d-x 4.0需要付出一定的努力和时间。以下是我对cocos2d-x 4.0学习之路的一些经验和建议。 1. 学习基础知识 在开始学习cocos2d-x 4.0之前,我们需要了解一些基础知识,比如C++语言、OpenGL ES等,这些都是cocos2d-x 4.0的底层实现技术。掌握这些基础知识会让我们从事游戏开发时更加得心应手。 2. 学习文档 学习cocos2d-x 4.0需要阅读官方文档,这些文档详细介绍了引擎的各个方面,而且是学习的最佳资料。文档里包括了引擎的安装、使用、开发以及调试等。建议大家先从官网下载文档,并且仔细阅读。 3. 实践和开发 掌握了基础知识以及学习了文档之后,最好的方式就是通过实践和开发来加深对cocos2d-x 4.0的理解。通过实际开发游戏来体验引擎的使用,这样能够更深刻的理解引擎的机制以及遇到的各种问题该如何解决。同时,通过找到一些相近的问题,并通过查阅文档、代码实现等方式来解决问题,可以增强自己的技术水平。 4. 参与社区 cocos2d-x 4.0的官方论坛以及社区非常活跃,里面的开发者也有着丰富的经验和技术,在学习中可以多向论坛、社区里的大牛请教,获得更多的技术指导,同时也可以参与讨论,提出自己的问题和思考来获得反馈,这样可以更快地提高自己的技术。 总之,学习cocos2d-x 4.0需要耐心和对游戏开发的热情。只有通过不断的学习与实践,我们才能最终掌握这个优秀的游戏开发引擎,从而创建属于自己的游戏作品。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

热血枫叶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值