存档

2012年9月 的存档

Cocos2d-x之CCImage深入分析

2012年9月29日 没有评论

 

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路 CSDN博客地址:http://blog.csdn.net/honghaier]  

红孩儿Cocos2d-X学习园地QQ群:249941957 加群写:Cocos2d-x

 

                  Cocos2d-xCCImage深入分析  

 

      本节所用Cocos2d-x版本:cocos2d-2.0-x-2.0.2

            CCImage类:支持从JPG,PNG,TIFF以及数据流,字符串中创建供Cocos2d-x进行访问的图片数据对象。

        这是一个非常重要的类,因为它是你使用cocos2d-x来加载图片的基础。目前此类只支持JPG,PNG,TIFF三种图像文件,对于这三种图像文件,CCImage分别使用libjpg,libpng,libtiff进行读取访问和写文件的操作。有兴趣的同学可以上网查询相关图像库的分析和使用,同时建议有能力的同学对此类进行扩展,令它支持更多的图像格式,是为“举一反三”。

        学之前最好先了解一下libjpg,libpng,libtiff等图片功能库的使用。可以参见文章底部的参考资料。

       现在,先来看CCImage图像头文件:

#ifndef __CC_IMAGE_H__
#define __CC_IMAGE_H__
//派生于CCObject
#include "cocoa/CCObject.h"
//Cocos2d命名空间
NS_CC_BEGIN

class CC_DLL CCImage : public CCObject
{
public:
	//构造函数
	CCImage();
	//析构函数
    ~CCImage();
	//支持的图片类型
    typedef enum
    {	
        kFmtJpg = 0,	//JPG
        kFmtPng,		//PNG
        kFmtTiff,		//TIFF
        kFmtRawData,	//数据流,要求为RGBA8888
        kFmtUnKnown	//无效
    }EImageFormat;
	//对齐方式
    typedef enum
    {
        kAlignCenter        = 0x33, //横向纵向都居中.
        kAlignTop           = 0x13, //横向居中,纵向居上.
        kAlignTopRight      = 0x12, //横向居右,纵向居上.
        kAlignRight         = 0x32, //横向居中,纵向居中.
        kAlignBottomRight   = 0x22, //横向居右,纵向居下.
        kAlignBottom        = 0x23, //横向居中,纵向居下.
        kAlignBottomLeft    = 0x21, //横向居左,纵向居下.
        kAlignLeft          = 0x31, //横向居左,纵向居中.
        kAlignTopLeft       = 0x11, //横向居左,纵向居上.
    }ETextAlign;

	//从指定的路径载入一个所支持的格式的图片文件。
    bool initWithImageFile(const char * strPath, EImageFormat imageType = kFmtPng);

	//从指定的路径载入一个所支持的格式的图片文件,但它是线程安全的,因此可以用在多线程加载图片。
    bool initWithImageFileThreadSafe(const char *fullpath, EImageFormat imageType = kFmtPng);

	//从内存中加载图片数据。
	//参1:指向图片数据所处内存地址的指针。
	//参2:图片数据长度
	//参3:数据对应图片的格式,
	//参4:数据对应图片的宽度
	//参5:数据对应图片的高度
	//参6:每像素的字节位数,即色深。
    bool initWithImageData(void * pData, 
                           int nDataLen, 
                           EImageFormat eFmt = kFmtUnKnown,
                           int nWidth = 0,
                           int nHeight = 0,
                           int nBitsPerComponent = 8);

	//从字符串创建图片数据。
	//参1:字符串
	//参2:要创建的图片宽度,如果填0,则按照字符串的宽度进行设置。
	//参3:要创建的图片高度,如果填0,则按照字符串的高度进行设置。
	//参4:文字的对齐方式。
	//参5:字体名称
	//参6:字体大小
    bool initWithString(
        const char *    pText, 
        int             nWidth = 0, 
        int             nHeight = 0,
        ETextAlign      eAlignMask = kAlignCenter,
        const char *    pFontName = 0,
        int             nSize = 0);

    //取得图像数据地址
	unsigned char *   getData()               { return m_pData; }
	//取得图像数据的长度
    int         getDataLen()            { return m_nWidth * m_nHeight; }
	//是否有Alpha通道。
	bool hasAlpha()                     { return m_bHasAlpha; }
	//是否有Alpha渐变
    bool isPremultipliedAlpha()         { return m_bPreMulti; }

	//将当前图片数据保存成指定的文件格式。
	//参1:绝对路径名
	//参2:是否保存成RGB格式
    bool saveToFile(const char *pszFilePath, bool bIsToRGB = true);
	//定义变量m_nWidth和get接口
	CC_SYNTHESIZE_READONLY(unsigned short,   m_nWidth,       Width);
	//定义变量m_nHeight和get接口
	CC_SYNTHESIZE_READONLY(unsigned short,   m_nHeight,      Height);
	//定义变量m_nBitsPerComponent和get接口
    CC_SYNTHESIZE_READONLY(int,     m_nBitsPerComponent,   BitsPerComponent);

protected:
	//读取JPG图片数据
	//参1:数据地址
	//参2:数据长度
	bool _initWithJpgData(void *pData, int nDatalen);
	//读取PNG图片数据到内存成成Cocos2d-x所用的图像数据保存到m_pData中
	bool _initWithPngData(void *pData, int nDatalen);
	//读取TIFF图片数据到内存成成Cocos2d-x所用的图像数据保存到m_pData中
         bool _initWithTiffData(void* pData, int nDataLen);
	//读取RGBA8888格式的图片数据。
	//参1:数据地址
	//参2:数据长度
	//参3:图片宽度
	//参4:图片高度
	//参5:图片色深
    bool _initWithRawData(void *pData, int nDatalen, int nWidth, int nHeight, int nBitsPerComponent);
	//将图像数据保存为PNG图片
	bool _saveImageToPNG(const char *pszFilePath, bool bIsToRGB = true);
	//将图像数据保存为JPG图片
    bool _saveImageToJPG(const char *pszFilePath);
	//图像数据地址
	unsigned char *m_pData;
	//是否有Alpha
	bool m_bHasAlpha;
	//是否有Alpha渐变    bool m_bPreMulti;

private:
    // 拷贝构造与重载等号拷贝
    CCImage(const CCImage&    rImg);
    CCImage & operator=(const CCImage&);
};

NS_CC_END

#endif    // __CC_IMAGE_H__

继续分析CPP文件,其CPP文件由两个文件构成:

CCImageCommon_cpp.h和CCImage.cpp。在CCImage.cpp的起始代码处:

//定义 基于平台的CCImage的CPP标记宏

#define __CC_PLATFORM_IMAGE_CPP__

//这里引用CCImageCommon_cpp.h

#include "platform/CCImageCommon_cpp.h"

    可以看到这里引用了头文件CCImageCommon_cpp.h。那我们就打开CCImageCommon_cpp.h:

#ifndef __CC_PLATFORM_IMAGE_CPP__
	//如果没有定义基于平台的CCImage的CPP标记宏,编译时打印出错。
#error "CCFileUtilsCommon_cpp.h can only be included for CCFileUtils.cpp in platform/win32(android,...)"
#endif /* __CC_PLATFORM_IMAGE_CPP__ */

#include "CCImage.h"
#include "CCCommon.h"
#include "CCStdC.h"
#include "CCFileUtils.h"
//libpng库的头文件
#include "png.h"
//libjpg库的头文件
#include "jpeglib.h"
//libtiff库的头文件
#include "tiffio.h"
#include <string>
#include <ctype.h>
//使用Cocos2d命名空间
NS_CC_BEGIN

//定义宏从RGB888或RGB5A1像素格式数据中返回一个RGBA8888的像素格式数据。
#define CC_RGB_PREMULTIPLY_APLHA(vr, vg, vb, va) /
    (unsigned)(((unsigned)((unsigned char)(vr) * ((unsigned char)(va) + 1)) >> 8) | /
    ((unsigned)((unsigned char)(vg) * ((unsigned char)(va) + 1) >> 8) << 8) | /
    ((unsigned)((unsigned char)(vb) * ((unsigned char)(va) + 1) >> 8) << 16) | /
    ((unsigned)(unsigned char)(va) << 24))

//图片文件数据的信息结构
typedef struct 
{
    unsigned char* data;
    int size;
    int offset;
}tImageSource;
//读取PNG文件数据的回调函数
//参1:PNG文件数据指针
//参2:返回的图片数据地址
//参3:要从PNG文件中读取的图片数据的长度,其值 = 每像素字节数X图片的宽X图片的高。
static void pngReadCallback(png_structp png_ptr, png_bytep data, png_size_t length)
{
	//从一个PNG文件数据指针指针中返回图片文件数据的信息结构
    tImageSource* isource = (tImageSource*)png_get_io_ptr(png_ptr);
	//如果要读取的长度有效。则将相应长度的图像数据拷贝到返回的图片数据地址中。
    if((int)(isource->offset + length) <= isource->size)
    {
        memcpy(data, isource->data+isource->offset, length);
        isource->offset += length;
    }
    else
    {
        png_error(png_ptr, "pngReaderCallback failed");
    }
}

//////////////////////////////////////////////////////////////////////////
//构造函数
CCImage::CCImage()
: m_nWidth(0)
, m_nHeight(0)
, m_nBitsPerComponent(0)
, m_pData(0)
, m_bHasAlpha(false)
, m_bPreMulti(false)
{

}
//析构函数
CCImage::~CCImage()
{	
	//释放图像数据占用的内存
    CC_SAFE_DELETE_ARRAY(m_pData);
}
//从指定的路径载入一个所支持的格式的图片文件。
bool CCImage::initWithImageFile(const char * strPath, EImageFormat eImgFmt/* = eFmtPng*/)
{
    bool bRet = false;
    unsigned long nSize = 0;
    //调用文件操作函数库中的函数读取相应路径的文件到内存中,并返回内存的地址给指针变量pBuffer。
	unsigned char* pBuffer = CCFileUtils::sharedFileUtils()->getFileData(CCFileUtils::sharedFileUtils()->fullPathFromRelativePath(strPath), "rb", &nSize);
    if (pBuffer != NULL && nSize > 0)
	{	
			//如果读取成功,则将内存地址做为参数调用initWithImageData函数来加载图片数据。
        bRet = initWithImageData(pBuffer, nSize, eImgFmt);
	}
	//释放读取文件所创建的内存。
    CC_SAFE_DELETE_ARRAY(pBuffer);
    return bRet;
}

//从指定的路径载入一个所支持的格式的图片文件,但它是线程安全的,因此可以用在多线程加载图片。
bool CCImage::initWithImageFileThreadSafe(const char *fullpath, EImageFormat imageType)
{
    bool bRet = false;
	unsigned long nSize = 0;
	//调用文件操作函数库中的函数读取相应路径的文件到内存中,并返回内存的地址给指针变量pBuffer。
    unsigned char *pBuffer = CCFileUtils::sharedFileUtils()->getFileData(fullpath, "rb", &nSize);
    if (pBuffer != NULL && nSize > 0)
	{
	//如果读取成功,则将内存地址做为参数调用initWithImageData函数来加载图片数据。
        bRet = initWithImageData(pBuffer, nSize, imageType);
	}
	//释放读取文件所创建的内存。
    CC_SAFE_DELETE_ARRAY(pBuffer);
    return bRet;
}

//从内存中加载图片数据。
//参1:指向图片数据所处内存地址的指针。
//参2:图片数据长度
//参3:数据对应图片的格式,
//参4:数据对应图片的宽度
//参5:数据对应图片的高度
//参6:每像素的字节位数,即色深。
bool CCImage::initWithImageData(void * pData, 
                                int nDataLen, 
                                EImageFormat eFmt/* = eSrcFmtPng*/, 
                                int nWidth/* = 0*/,
                                int nHeight/* = 0*/,
                                int nBitsPerComponent/* = 8*/)
{
    bool bRet = false;
    do 
	{
		    //参数有效性判断
        CC_BREAK_IF(! pData || nDataLen <= 0);
	    //根据不同的图片数据格式调用不同的函数创建相应的图片数据。
        if (kFmtPng == eFmt)
        {	
		   //读取PNG格式的图片数据。
            bRet = _initWithPngData(pData, nDataLen);
            break;
        }
        else if (kFmtJpg == eFmt)
        {
			//读取JPG格式的图片数据。
            bRet = _initWithJpgData(pData, nDataLen);
            break;
        }
        else if (kFmtTiff == eFmt)
        {
			//读取TIFF格式的图片数据。
            bRet = _initWithTiffData(pData, nDataLen);
            break;
        }
        else if (kFmtRawData == eFmt)
        {
			//读取RGBA8888格式的图片数据。
            bRet = _initWithRawData(pData, nDataLen, nWidth, nHeight, nBitsPerComponent);
            break;
        }
        else
        {
            // 如果未指定数据的格式.则通过对比相应格式的文件头信息判断格式。
		   //判断是否是PNG
            if (nDataLen > 8)
            {
                unsigned char* pHead = (unsigned char*)pData;
                if (   pHead[0] == 0x89
                    && pHead[1] == 0x50
                    && pHead[2] == 0x4E
                    && pHead[3] == 0x47
                    && pHead[4] == 0x0D
                    && pHead[5] == 0x0A
                    && pHead[6] == 0x1A
                    && pHead[7] == 0x0A)
                {
			       //通过对比如果是属于PNG格式则读取PNG格式的图片数据
                    bRet = _initWithPngData(pData, nDataLen);
                    break;
                }
            }
			//判断是否是TIFF
            if (nDataLen > 2)
            {
                unsigned char* pHead = (unsigned char*)pData;
                if (  (pHead[0] == 0x49 && pHead[1] == 0x49)
                    || (pHead[0] == 0x4d && pHead[1] == 0x4d)
                    )
                {   //通过对比如果是属于TIFF格式则读取TIFF格式的图片数据
                    bRet = _initWithTiffData(pData, nDataLen);
                    break;
                }
            }
			//判断是否是JPG
            if (nDataLen > 2)
            {
                unsigned char* pHead = (unsigned char*)pData;
                if (   pHead[0] == 0xff
                    && pHead[1] == 0xd8)
                {
                    bRet = _initWithJpgData(pData, nDataLen);
                    break;
                }
            }
        }
    } while (0);
    return bRet;
}
//读取JPG格式的图片数据。
bool CCImage::_initWithJpgData(void * data, int nSize)
{
	//此处使用libjpeg库来读取JPG,这里需要建立相应的结构变量。
    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;
    JSAMPROW row_pointer[1] = {0};
    unsigned long location = 0;
    unsigned int i = 0;

    bool bRet = false;
    do 
    {
        //下面是使用libjpeg来进行JPG格式数据的读取。

        cinfo.err = jpeg_std_error( &jerr );
        jpeg_create_decompress( &cinfo );
        jpeg_mem_src( &cinfo, (unsigned char *) data, nSize );
        jpeg_read_header( &cinfo, true );

        // JPG只能支持RGB的像素格式
        if (cinfo.jpeg_color_space != JCS_RGB)
        {
            if (cinfo.jpeg_color_space == JCS_GRAYSCALE || cinfo.jpeg_color_space == JCS_YCbCr)
            {
                cinfo.out_color_space = JCS_RGB;
            }
        }
        else
        {
            break;
        }
	    //开始解压JPG。
        jpeg_start_decompress( &cinfo );

        //设置相应成员变量。
        m_nWidth  = (short)(cinfo.image_width);
        m_nHeight = (short)(cinfo.image_height);
        m_bHasAlpha = false;
        m_bPreMulti = false;
        m_nBitsPerComponent = 8;
        row_pointer[0] = new unsigned char[cinfo.output_width*cinfo.output_components];
        CC_BREAK_IF(! row_pointer[0]);
	   //为图片数据指针申请相应大小的内存。
        m_pData = new unsigned char[cinfo.output_width*cinfo.output_height*cinfo.output_components];
        CC_BREAK_IF(! m_pData);

      //将像素信息读取到图片数据指针指向的内存中。
        while( cinfo.output_scanline < cinfo.image_height )
        {
		   //每次读取一个扫描行的像素信息
            jpeg_read_scanlines( &cinfo, row_pointer, 1 );
            for( i=0; i<cinfo.image_width*cinfo.output_components;i++) 
            {
			   //将读取到的像素信息存入相应的内存中。
                m_pData[location++] = row_pointer[0][i];
            }
        }
	    //完成解压
        jpeg_finish_decompress( &cinfo );
	    //释放所用的结构
        jpeg_destroy_decompress( &cinfo );      
        bRet = true;
    } while (0);
	//释放申请的内存
    CC_SAFE_DELETE_ARRAY(row_pointer[0]);
    return bRet;
}
//读取PNG格式的图片数据。
bool CCImage::_initWithPngData(void * pData, int nDatalen)
{
// PNG文件头信息长度值
#define PNGSIGSIZE  8
	bool bRet = false;
	//定义存储PNG文件头信息的BYTE数组
	png_byte        header[PNGSIGSIZE]   = {0}; 	
	//PNG的读取说明结构,这里是libpng用到的结构体。
	png_structp     png_ptr     =   0;
	//PNG的信息结构
    png_infop       info_ptr    = 0;
	
    do 
    {
        // PNG文件头有效性判断
        CC_BREAK_IF(nDatalen < PNGSIGSIZE);

        // 存储文件头信息
        memcpy(header, pData, PNGSIGSIZE);
        CC_BREAK_IF(png_sig_cmp(header, 0, PNGSIGSIZE));

        //初始化PNG的读取说明结构
        png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
        CC_BREAK_IF(! png_ptr);

        // 初始化 PNG信息结构
        info_ptr = png_create_info_struct(png_ptr);
        CC_BREAK_IF(!info_ptr);

#if (CC_TARGET_PLATFORM != CC_PLATFORM_BADA)
        CC_BREAK_IF(setjmp(png_jmpbuf(png_ptr)));
#endif

        //图片文件数据的信息结构
        tImageSource imageSource;
        imageSource.data    = (unsigned char*)pData;
        imageSource.size    = nDatalen;
        imageSource.offset  = 0;
	   //设置读取PNG的回调函数
        png_set_read_fn(png_ptr, &imageSource, pngReadCallback);

        //读取PNG信息结构
        png_read_info(png_ptr, info_ptr);
        //取得图片数据的相关属性。
        m_nWidth = png_get_image_width(png_ptr, info_ptr);
        m_nHeight = png_get_image_height(png_ptr, info_ptr);
        m_nBitsPerComponent = png_get_bit_depth(png_ptr, info_ptr);
        png_uint_32 color_type = png_get_color_type(png_ptr, info_ptr);
	    //打印像素格式
        //CCLOG("color type %u", color_type);
        
        // 如果是调色板格式的PNG,将其转为RGB888的像素格式。
        if (color_type == PNG_COLOR_TYPE_PALETTE)
        {
            png_set_palette_to_rgb(png_ptr);
        }
        // 如果是像素格式少于1字节长度的灰度图,将其转为每像素占1字节的像素格式。
        if (color_type == PNG_COLOR_TYPE_GRAY && m_nBitsPerComponent < 8)
        {
            png_set_expand_gray_1_2_4_to_8(png_ptr);
        }
        // 将RNS块数据信息扩展为完整的ALPHA通道信息
        if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
        {
            png_set_tRNS_to_alpha(png_ptr);
        }  
        // reduce images with 16-bit samples to 8 bits
        if (m_nBitsPerComponent == 16)
        {
            png_set_strip_16(png_ptr);            
        } 
        // 将灰度图格式扩展成RGB
        if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
        {
            png_set_gray_to_rgb(png_ptr);
        }

        // 读取PNG数据
        // m_nBitsPerComponent will always be 8
        m_nBitsPerComponent = 8;
        png_uint_32 rowbytes;
	   //后面读取PNG信息是按行读取,将每一行的像素数据读取到相应内存块中,下面这个BYTE指针数组就是为了存储每行图片像素信息读取到的相应内存块位置。
        png_bytep* row_pointers = (png_bytep*)malloc( sizeof(png_bytep) * m_nHeight );
        
        png_read_update_info(png_ptr, info_ptr);
        //取得图片每一行像素的字节数量
        rowbytes = png_get_rowbytes(png_ptr, info_ptr);
        //为图片数据申请内存。
        m_pData = new unsigned char[rowbytes * m_nHeight];
        CC_BREAK_IF(!m_pData);
        //将申请到的内存地址按行放入相应的读取结构中
        for (unsigned short i = 0; i < m_nHeight; ++i)
        {
            row_pointers[i] = m_pData + i*rowbytes;
        }
	    //将图片文件数据读取到相应的内存地址。
        png_read_image(png_ptr, row_pointers);
        //结束读取。
        png_read_end(png_ptr, NULL);
        //计算每像素占字节数。不管是RGB888还是RGBA8888的像素格式,其实际每像素占用的字节数均是4,只不过RGB888中多余的1字节不被用到。
        png_uint_32 channel = rowbytes/m_nWidth;
        if (channel == 4)
        {
		   //设置为带ALPHA通道
            m_bHasAlpha = true;
		   //定义指针变量tmp指向图像数据地址。用于在后面存放图像数据。
            unsigned int *tmp = (unsigned int *)m_pData;
		   //双循环遍历像素数据。
            for(unsigned short i = 0; i < m_nHeight; i++)
            {
                for(unsigned int j = 0; j < rowbytes; j += 4)
                {
				   //将R,G,B,A四个BYTE值组成一个DWORD值。
                    *tmp++ = CC_RGB_PREMULTIPLY_APLHA( row_pointers[i][j], row_pointers[i][j + 1], 
                                                      row_pointers[i][j + 2], row_pointers[i][j + 3] );
                }
            }
            //设置使用渐变ALPHA
            m_bPreMulti = true;
        }
	   //释放row_pointers
        CC_SAFE_FREE(row_pointers);

        bRet = true;
    } while (0);

    if (png_ptr)
	{
		    //释放png_ptr
        png_destroy_read_struct(&png_ptr, (info_ptr) ? &info_ptr : 0, 0);
    }
    return bRet;
}
//读取TIFF图片数据时的回调函数。
//参1:文件数据内存。
//参2:输出参数,读取到的图像数据复制到对应的内存地址中。
//参3:图片数据长度。
static tmsize_t _tiffReadProc(thandle_t fd, void* buf, tmsize_t size)
{
	//将fd转化为图片文件数据的信息结构指针。
    tImageSource* isource = (tImageSource*)fd;
    uint8* ma;
    uint64 mb;
	//定义一次可以读取的数据长度。
    unsigned long n;
	//定义变量o统计每次循环读取的数据长度。
    unsigned long o;
	//定义变量统计读取完的数据长度。
    tmsize_t p;
	//让前面定义的uint8类型指针变量ma指向buf。用于在后面存放图像数据。
    ma=(uint8*)buf;
	//让前面定义的变量mb来统计剩余未读取的数据长度。
    mb=size;
    p=0;
	//使用while循环进行读取,判断条件为剩余未读的数据长度是否大于0。
    while (mb>0)
    {
        n=0x80000000UL;
        if ((uint64)n>mb)
            n=(unsigned long)mb;

		//如果尚未读完所有数据,则继续读取,否则出错返回0
        if((int)(isource->offset + n) <= isource->size)
        {
            memcpy(ma, isource->data+isource->offset, n);
            isource->offset += n;
            o = n;
        }
        else
        {
            return 0;
        }
	    //读取完长度为o的数据,则对指针进行相应的偏移操作供下次进行读取操作。
        ma+=o;
	   //更新未读取的剩余长度
        mb-=o;
	   //更新读取完的数量长度
        p+=o;
		//下面这个if比较奇怪,因为是不可能为true的。在上一个if判断中已经设置了o=n。
        if (o!=n)
        {
            break;
        }
    }
    return p;
}
//将数据保存为tiff图像文件所调用的回调函数。这里未用。
static tmsize_t _tiffWriteProc(thandle_t fd, void* buf, tmsize_t size)
{
    CC_UNUSED_PARAM(fd);
    CC_UNUSED_PARAM(buf);
    CC_UNUSED_PARAM(size);
    return 0;
}

//在对TIFF图像文件进行解析时进行重定位时调用的回调函数。
static uint64 _tiffSeekProc(thandle_t fd, uint64 off, int whence)
{
	//将fd转化为图片文件数据的信息结构指针。
    tImageSource* isource = (tImageSource*)fd;
    uint64 ret = -1;
    do 
	{
		   //如果定位方式为从头开始计算
        if (whence == SEEK_SET)
        {
            CC_BREAK_IF(off > isource->size-1);
            ret = isource->offset = (uint32)off;
        }
        else if (whence == SEEK_CUR)
        {  //如果定位方式为从当前位置开始计算
            CC_BREAK_IF(isource->offset + off > isource->size-1);
            ret = isource->offset += (uint32)off;
        }
        else if (whence == SEEK_END)
        {   //如果定位方工业从文件尾部开始计算
            CC_BREAK_IF(off > isource->size-1);
            ret = isource->offset = (uint32)(isource->size-1 - off);
        }
        else
        {//其它方式也按照从头开始计算
            CC_BREAK_IF(off > isource->size-1);
            ret = isource->offset = (uint32)off;
        }
    } while (0);

    return ret;
}
//取得tiff图片文件大小的回调函数。
static uint64 _tiffSizeProc(thandle_t fd)
{
    tImageSource* pImageSrc = (tImageSource*)fd;
    return pImageSrc->size;
}
//关闭tiff图片文件读取的回调函数。
static int _tiffCloseProc(thandle_t fd)
{
    CC_UNUSED_PARAM(fd);
    return 0;
}
//将tiff图片文件映射到内存时调用的回调函数。
static int _tiffMapProc(thandle_t fd, void** pbase, toff_t* psize)
{
    CC_UNUSED_PARAM(fd);
    CC_UNUSED_PARAM(pbase);
    CC_UNUSED_PARAM(psize);
    return 0;
}
//解除tiff图片映射到内存的回调函数。
static void _tiffUnmapProc(thandle_t fd, void* base, toff_t size)
{
    CC_UNUSED_PARAM(fd);
    CC_UNUSED_PARAM(base);
    CC_UNUSED_PARAM(size);
}
//使用LibTiff读取TIFF格式的图片数据。
bool CCImage::_initWithTiffData(void* pData, int nDataLen)
{
    bool bRet = false;
    do 
    {
        //设置图片文件数据的信息结构
        tImageSource imageSource;
        imageSource.data    = (unsigned char*)pData;
        imageSource.size    = nDataLen;
        imageSource.offset  = 0;
	    //使用libtiff打开一个tif文件,设置对其进行操作的各行为的回调函数。如果成功打开文件返回一个TIFF结构指针。
        TIFF* tif = TIFFClientOpen("file.tif", "r", (thandle_t)&imageSource, 
            _tiffReadProc, _tiffWriteProc,
            _tiffSeekProc, _tiffCloseProc, _tiffSizeProc,
            _tiffMapProc,
            _tiffUnmapProc);
        //有效性判断。
        CC_BREAK_IF(NULL == tif);
        
        uint32 w = 0, h = 0;
        uint16 bitsPerSample = 0, samplePerPixel = 0, planarConfig = 0;
	   //定义变量nPixels存储图像数据像素数量。
        size_t npixels = 0;
        //读取相应的图片属性信息。
	    //图片宽度。
        TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &w);
		//图片高度。
        TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h);
		//图片色深。
        TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bitsPerSample);
		//每像素数据占的字节数
        TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &samplePerPixel);
		//图像的平面配置
        TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &planarConfig);
		//取得像素数量
        npixels = w * h;
        //设置带ALPHA通道。
        m_bHasAlpha = true;
        m_nWidth = w;
        m_nHeight = h;
        m_nBitsPerComponent = 8;
	   //申请相应的内存用来存储像素数据。
        m_pData = new unsigned char[npixels * sizeof (uint32)];
	   //申请临时内存进行TIFF数据读取。
        uint32* raster = (uint32*) _TIFFmalloc(npixels * sizeof (uint32));
        if (raster != NULL) 
        {
		  //读取TIFF数据
           if (TIFFReadRGBAImageOriented(tif, w, h, raster, ORIENTATION_TOPLEFT, 0))
           {
			   //下面是从TIFF数据中解析生成我们Cocos2d-x所用的图像数据。
			  //这里定义指针变量指向TIFF数据
                unsigned char* src = (unsigned char*)raster;
			  //这里定义指针变量指向我们Cocos2d-x所用的图像数据
                unsigned int* tmp = (unsigned int*)m_pData;

	/*
                //遍历每像素进行,对像素的RGBA值进行组合,将组合成的DWORD值写入到图像数据中。
                for(int j = 0; j < m_nWidth * m_nHeight * 4; j += 4)
                {
                    *tmp++ = CC_RGB_PREMULTIPLY_APLHA( src[j], src[j + 1], 
                        src[j + 2], src[j + 3] );
                }
	*/
	//ALPHA通道有效。
                m_bPreMulti = true;
			  //上面循环将组合后的DWORD值写入图像数据太慢,这里直接进行内存拷贝可以达到同样目的。
               memcpy(m_pData, raster, npixels*sizeof (uint32));
           }
		  //释放临时申请的内存。
          _TIFFfree(raster);
        }
        

		//关闭TIFF文件读取。
        TIFFClose(tif);

        bRet = true;
    } while (0);
    return bRet;
}
//读取RGBA8888格式的图片数据。
//参1:数据地址
//参2:数据长度
//参3:图片宽度
//参4:图片高度
//参5:图片色深
bool CCImage::_initWithRawData(void * pData, int nDatalen, int nWidth, int nHeight, int nBitsPerComponent)
{
    bool bRet = false;
    do 
    {
	   //宽高有效性判断
        CC_BREAK_IF(0 == nWidth || 0 == nHeight);
	   //保存相关属性
        m_nBitsPerComponent = nBitsPerComponent;
        m_nHeight   = (short)nHeight;
        m_nWidth    = (short)nWidth;
        m_bHasAlpha = true;

        // 只支持 RGBA8888 格式
        int nBytesPerComponent = 4;
        int nSize = nHeight * nWidth * nBytesPerComponent;
	    //为图像数据申请相应内存,将地址返回给m_pData。 
        m_pData = new unsigned char[nSize];
	    //内存申请成败判断
        CC_BREAK_IF(! m_pData);
	   //将参数数据拷贝到m_pData指向的内存地址中。
        memcpy(m_pData, pData, nSize);

        bRet = true;
    } while (0);
    return bRet;
}
//将图像数据保存为图片文件,目前只支持PNG和JPG
//参1:文件路径
//参2:是否是RGB的像素格式
bool CCImage::saveToFile(const char *pszFilePath, bool bIsToRGB)
{
    bool bRet = false;

    do 
    {
	   //参数有效性判断
        CC_BREAK_IF(NULL == pszFilePath);
	   //通过是否有扩展名判断参数有效性。
        std::string strFilePath(pszFilePath);
        CC_BREAK_IF(strFilePath.size() <= 4);
	   //将路径名转为小写字符串
        std::string strLowerCasePath(strFilePath);
        for (unsigned int i = 0; i < strLowerCasePath.length(); ++i)
        {
            strLowerCasePath[i] = tolower(strFilePath[i]);
        }
	    //通过扩展名转成相应的图片文件
		//PNG
        if (std::string::npos != strLowerCasePath.find(".png"))
        {
            CC_BREAK_IF(!_saveImageToPNG(pszFilePath, bIsToRGB));
        }
	    //JPG
        else if (std::string::npos != strLowerCasePath.find(".jpg"))
        {
            CC_BREAK_IF(!_saveImageToJPG(pszFilePath));
        }
        else
        {
		   //不支持其它格式
            break;
        }

        bRet = true;
    } while (0);

    return bRet;
}
//将图像数据保存为PNG图片
bool CCImage::_saveImageToPNG(const char * pszFilePath, bool bIsToRGB)
{
    bool bRet = false;
    do 
    {
	    //参数有效性判断
        CC_BREAK_IF(NULL == pszFilePath);
	   //使用libpng来写PNG文件。
	   //定义文件指针变量用于写文件
        FILE *fp;
	   //定义libpng所用的一些信息结构
        png_structp png_ptr;
        png_infop info_ptr;
        png_colorp palette;
        png_bytep *row_pointers;
	   //打开文件开始写入
        fp = fopen(pszFilePath, "wb");
        CC_BREAK_IF(NULL == fp);
	   //创建写PNG的文件结构体,将其结构指针返回给png_ptr
        png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	   //指针有效性判断
        if (NULL == png_ptr)
        {
            fclose(fp);
            break;
        }
	   //创建PNG的信息结构体,将其结构指针返回给info_ptr。
        info_ptr = png_create_info_struct(png_ptr);
        if (NULL == info_ptr)
        {
            fclose(fp);
            png_destroy_write_struct(&png_ptr, NULL);
            break;
        }
#if (CC_TARGET_PLATFORM != CC_PLATFORM_BADA)
        if (setjmp(png_jmpbuf(png_ptr)))
        {
            fclose(fp);
            png_destroy_write_struct(&png_ptr, &info_ptr);
            break;
        }
#endif
	    //初始化png_ptr
        png_init_io(png_ptr, fp);
	    //根据是否有ALPHA来写入相应的头信息
        if (!bIsToRGB && m_bHasAlpha)
        {
            png_set_IHDR(png_ptr, info_ptr, m_nWidth, m_nHeight, 8, PNG_COLOR_TYPE_RGB_ALPHA,
                PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
        } 
        else
        {
            png_set_IHDR(png_ptr, info_ptr, m_nWidth, m_nHeight, 8, PNG_COLOR_TYPE_RGB,
                PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
        }
	    //创建调色板
        palette = (png_colorp)png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH * sizeof (png_color));
	   //设置调色板
        png_set_PLTE(png_ptr, info_ptr, palette, PNG_MAX_PALETTE_LENGTH);
	    //写入info_ptr
        png_write_info(png_ptr, info_ptr);
	    //
        png_set_packing(png_ptr);
	    //申请临时存储m_pData中每一行像素数据地址的内存空间,将申请到的内存地址返回给row_pointers。
        row_pointers = (png_bytep *)malloc(m_nHeight * sizeof(png_bytep));
        if(row_pointers == NULL)
        {
            fclose(fp);
            png_destroy_write_struct(&png_ptr, &info_ptr);
            break;
        }
	    //根据是否有ALPHA分别处理写入像素数据到文件中。
        if (!m_bHasAlpha)
        {
		   //如果没有ALPHA,只是RGB,这里将m_pData中数据,遍历每一行,将每一行的起始内存地址放入row_pointers指针数组中。
            for (int i = 0; i < (int)m_nHeight; i++)
            {
                row_pointers[i] = (png_bytep)m_pData + i * m_nWidth * 3;
            }
		   //将row_pointers中指向的每一行数据写入文件。
            png_write_image(png_ptr, row_pointers);

            //释放内存
	free(row_pointers);
            row_pointers = NULL;
        }
        else
        {
		   //如果带ALPHA通道。对是否是RGB格式又进行分别处理。
		   //如果是RGB888格式
            if (bIsToRGB)
            {
			   //创建临时的内存存放像素数据。每个像素3字节,分别存R,G,B值。
                unsigned char *pTempData = new unsigned char[m_nWidth * m_nHeight * 3];
                if (NULL == pTempData)
                {
                    fclose(fp);
                    png_destroy_write_struct(&png_ptr, &info_ptr);
                    break;
                }
			  //双循环遍历每个像素,将R,G,B值保存到数组中。
                for (int i = 0; i < m_nHeight; ++i)
                {
                    for (int j = 0; j < m_nWidth; ++j)
                    {
                        pTempData[(i * m_nWidth + j) * 3] = m_pData[(i * m_nWidth + j) * 4];
                        pTempData[(i * m_nWidth + j) * 3 + 1] = m_pData[(i * m_nWidth + j) * 4 + 1];
                        pTempData[(i * m_nWidth + j) * 3 + 2] = m_pData[(i * m_nWidth + j) * 4 + 2];
                    }
                }
			  //将数组中保存的每行像素的内存地址存入row_pointers数组中。
                for (int i = 0; i < (int)m_nHeight; i++)
                {
                    row_pointers[i] = (png_bytep)pTempData + i * m_nWidth * 3;
                }
			  //将row_pointers中指向的每一行数据写入文件。
                png_write_image(png_ptr, row_pointers);
            	  //释放内存
                free(row_pointers);
                row_pointers = NULL;
			   
                CC_SAFE_DELETE_ARRAY(pTempData);
            } 
            else
            {
			   //如果是RGBA8888格式
			   //将数组中保存的每行像素的内存地址存入row_pointers数组中。
                for (int i = 0; i < (int)m_nHeight; i++)
                {
                    row_pointers[i] = (png_bytep)m_pData + i * m_nWidth * 4;
                }
				//将row_pointers中指向的每一行数据写入文件。
                png_write_image(png_ptr, row_pointers);
               //释放内存

                free(row_pointers);
                row_pointers = NULL;
            }
        }
		//结束写PNG文件
        png_write_end(png_ptr, info_ptr);
		//释放相应的信息结构
        png_free(png_ptr, palette);
        palette = NULL;
        png_destroy_write_struct(&png_ptr, &info_ptr);

        fclose(fp);

        bRet = true;
    } while (0);
    return bRet;
}

//将图像数据保存为JPG文件
bool CCImage::_saveImageToJPG(const char * pszFilePath)
{
    bool bRet = false;
    do 
    {
	    //参数有效性判断
        CC_BREAK_IF(NULL == pszFilePath);
		//使用libjpg库要用到的相关结构。
        struct jpeg_compress_struct cinfo;
        struct jpeg_error_mgr jerr;
        FILE * outfile;                 /* target file */
        JSAMPROW row_pointer[1];        /* pointer to JSAMPLE row[s] */
        int     row_stride;          /* physical row width in image buffer */
		//初始化相关结构
        cinfo.err = jpeg_std_error(&jerr);
        jpeg_create_compress(&cinfo);
		//开始写入文件
        CC_BREAK_IF((outfile = fopen(pszFilePath, "wb")) == NULL);
        	//写入JPG头文件基本信息
        jpeg_stdio_dest(&cinfo, outfile);
		//填充JPG图像的属性信息结构
        cinfo.image_width = m_nWidth;   	
        cinfo.image_height = m_nHeight;
        cinfo.input_components = 3;      	
        cinfo.in_color_space = JCS_RGB;   	
		//将信息结构来设置JPG图像
        jpeg_set_defaults(&cinfo);
		//开始进行数据压缩输出
        jpeg_start_compress(&cinfo, TRUE);
		//设置每行的字节长度
        row_stride = m_nWidth * 3;
		//跟据图像数据是否有ALPHA通道来进行分别处理
        if (m_bHasAlpha)
        {
		   //创建内存来存放图像的像素数据。
            unsigned char *pTempData = new unsigned char[m_nWidth * m_nHeight * 3];
            if (NULL == pTempData)
            {
                jpeg_finish_compress(&cinfo);
                jpeg_destroy_compress(&cinfo);
                fclose(outfile);
                break;
            }
			//双循环遍历每一个图像像素的数据,取R,G,B值存放到临时申请的内存地址中,A值弃之不取。
            for (int i = 0; i < m_nHeight; ++i)
            {
                for (int j = 0; j < m_nWidth; ++j)

                {
				  //因图像数据有A通道,所以找相应的像素地址时会由像素索引x4。
                    pTempData[(i * m_nWidth + j) * 3] = m_pData[(i * m_nWidth + j) * 4];
                    pTempData[(i * m_nWidth + j) * 3 + 1] = m_pData[(i * m_nWidth + j) * 4 + 1];
                    pTempData[(i * m_nWidth + j) * 3 + 2] = m_pData[(i * m_nWidth + j) * 4 + 2];
                }
            }
		   //将扫描行的数据写入JPG文件
            while (cinfo.next_scanline < cinfo.image_height) {
                row_pointer[0] = & pTempData[cinfo.next_scanline * row_stride];
                (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
            }

            CC_SAFE_DELETE_ARRAY(pTempData);
        } 
        else
        { //将扫描行的数据写入JPG文件
            while (cinfo.next_scanline < cinfo.image_height) {
                row_pointer[0] = & m_pData[cinfo.next_scanline * row_stride];
                (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
            }
        }
		//结束数据压缩,关闭文件并释放相应信息结构。
        jpeg_finish_compress(&cinfo);
        fclose(outfile);
        jpeg_destroy_compress(&cinfo);
        
        bRet = true;
    } while (0);
    return bRet;
}

NS_CC_END

现在我们回到CCImage.cpp:

#define __CC_PLATFORM_IMAGE_CPP__
#include "platform/CCImageCommon_cpp.h"

NS_CC_BEGIN

//此处定义一个BitmapDC类在位图上进行文字绘制。
class BitmapDC
{
public:
	//构造函数
    BitmapDC(HWND hWnd = NULL)
    : m_hDC(NULL)
    , m_hBmp(NULL)
    , m_hFont((HFONT)GetStockObject(DEFAULT_GUI_FONT))
    , m_hWnd(NULL)
	{
	//保存窗口句柄
        m_hWnd = hWnd;
        //取得窗口的hdc
        HDC hdc = GetDC(hWnd);
        //创建兼容的hdc
        m_hDC   = CreateCompatibleDC(hdc);
        //释放hdc
        ReleaseDC(hWnd, hdc);
    }
	//析构函数
    ~BitmapDC()
    {
        prepareBitmap(0, 0);
        if (m_hDC)
        {
            DeleteDC(m_hDC);
        }
        //创建字体
        HFONT hDefFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
        if (hDefFont != m_hFont)
        {
            DeleteObject(m_hFont);
            m_hFont = hDefFont;
        }
		// release temp font resource	
		if (m_curFontPath.size() > 0)
		{
			wchar_t * pwszBuffer = utf8ToUtf16(m_curFontPath);
			if (pwszBuffer)
			{
				RemoveFontResource(pwszBuffer);
				SendMessage( m_hWnd, WM_FONTCHANGE, 0, 0);
				delete [] pwszBuffer;
				pwszBuffer = NULL;
			}
		}
    }
    //多字节转宽字符
	wchar_t * utf8ToUtf16(std::string nString)
	{
		wchar_t * pwszBuffer = NULL;
		do 
		{
			if (nString.size() < 0)
			{
				break;
			}
			// utf-8 to utf-16
			int nLen = nString.size();
			int nBufLen  = nLen + 1;			
			pwszBuffer = new wchar_t[nBufLen];
			CC_BREAK_IF(! pwszBuffer);
			memset(pwszBuffer,0,nBufLen);
			nLen = MultiByteToWideChar(CP_UTF8, 0, nString.c_str(), nLen, pwszBuffer, nBufLen);		
			pwszBuffer[nLen] = '/0';
		} while (0);	
		return pwszBuffer;

	}
	//设置使用的字体和大小
    bool setFont(const char * pFontName = NULL, int nSize = 0)
    {
        bool bRet = false;
        do 
        {
            //创建字体
            std::string fontName = pFontName;
            std::string fontPath;
            HFONT       hDefFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
            LOGFONTA    tNewFont = {0};
            LOGFONTA    tOldFont = {0};
            GetObjectA(hDefFont, sizeof(tNewFont), &tNewFont);
            if (fontName.c_str())
            {    
                // 如果字体名称是ttf文件,取得其全路径名
                int nFindttf = fontName.find(".ttf");
                int nFindTTF = fontName.find(".TTF");
                if (nFindttf >= 0 || nFindTTF >= 0)
                {
                    fontPath = CCFileUtils::sharedFileUtils()->fullPathFromRelativePath(fontName.c_str());
                    int nFindPos = fontName.rfind("/");
                    fontName = &fontName[nFindPos+1];
                    nFindPos = fontName.rfind(".");
                    fontName = fontName.substr(0,nFindPos);                
                }
                tNewFont.lfCharSet = DEFAULT_CHARSET;
                strcpy_s(tNewFont.lfFaceName, LF_FACESIZE, fontName.c_str());
            }
            if (nSize)
            {
                tNewFont.lfHeight = -nSize;
            }
            //
            GetObjectA(m_hFont,  sizeof(tOldFont), &tOldFont);
            
            if (tOldFont.lfHeight == tNewFont.lfHeight
                && ! strcpy(tOldFont.lfFaceName, tNewFont.lfFaceName))
            {
                // already has the font 
                bRet = true;
                break;
            }

            // 删除旧的字体
            if (m_hFont != hDefFont)
            {
                DeleteObject(m_hFont);
				// release old font register
				if (m_curFontPath.size() > 0)
				{
					wchar_t * pwszBuffer = utf8ToUtf16(m_curFontPath);
					if (pwszBuffer)
					{
						if(RemoveFontResource(pwszBuffer))
						{
							SendMessage( m_hWnd, WM_FONTCHANGE, 0, 0);
						}						
						delete [] pwszBuffer;
						pwszBuffer = NULL;
					}
				}
				fontPath.size()>0?(m_curFontPath = fontPath):(m_curFontPath.clear());

				if (m_curFontPath.size() > 0)
				{
					wchar_t * pwszBuffer = utf8ToUtf16(m_curFontPath);
					if (pwszBuffer)
					{
						if(AddFontResource(pwszBuffer))
						{
							SendMessage( m_hWnd, WM_FONTCHANGE, 0, 0);
						}						
						delete [] pwszBuffer;
						pwszBuffer = NULL;
					}
				}
            }
            m_hFont = NULL;

            // 字体不使用锐利字体
            tNewFont.lfQuality = ANTIALIASED_QUALITY;

            // 创建新字体
            m_hFont = CreateFontIndirectA(&tNewFont);
            if (! m_hFont)
            {
                // create failed, use default font
                m_hFont = hDefFont;
                break;
            }
            
            bRet = true;
        } while (0);
        return bRet;
    }
	//取得使用当前字体写出的字符串所占据的图像大小。
    SIZE sizeWithText(const wchar_t * pszText, int nLen, DWORD dwFmt, LONG nWidthLimit)
    {
        SIZE tRet = {0};
        do 
        {
            CC_BREAK_IF(! pszText || nLen <= 0);

            RECT rc = {0, 0, 0, 0};
            DWORD dwCalcFmt = DT_CALCRECT;

            if (nWidthLimit > 0)
            {
                rc.right = nWidthLimit;
                dwCalcFmt |= DT_WORDBREAK
                    | (dwFmt & DT_CENTER)
                    | (dwFmt & DT_RIGHT);
            }
            // 使用当前字体。
            HGDIOBJ hOld = SelectObject(m_hDC, m_hFont);

            // 写字。
            DrawTextW(m_hDC, pszText, nLen, &rc, dwCalcFmt);
            SelectObject(m_hDC, hOld);

            tRet.cx = rc.right;
            tRet.cy = rc.bottom;
        } while (0);

        return tRet;
    }
    //创建一个指定大小的位图。
    bool prepareBitmap(int nWidth, int nHeight)
    {
        // 释放原来的位图
        if (m_hBmp)
        {
            DeleteObject(m_hBmp);
            m_hBmp = NULL;
        }		//如果大小有效则创建位图。
        if (nWidth > 0 && nHeight > 0)
        {
            m_hBmp = CreateBitmap(nWidth, nHeight, 1, 32, NULL);
            if (! m_hBmp)
            {
                return false;
            }
        }
        return true;
    }
     //在指定位置按照指定对齐方式写字。
	//参1:字符串
	//参2:指定拉置及大小
	//参3:文字对齐方式
    int drawText(const char * pszText, SIZE& tSize, CCImage::ETextAlign eAlign)
    {
        int nRet = 0;
        wchar_t * pwszBuffer = 0;
        do 
        {
            CC_BREAK_IF(! pszText);

            DWORD dwFmt = DT_WORDBREAK;
            DWORD dwHoriFlag = eAlign & 0x0f;
            DWORD dwVertFlag = (eAlign & 0xf0) >> 4;
            //设置横向对齐方式。
            switch (dwHoriFlag)
            {
            case 1: // 左对齐
                dwFmt |= DT_LEFT;
                break;
            case 2: // 右对齐
                dwFmt |= DT_RIGHT;
                break;
            case 3: // 居中
                dwFmt |= DT_CENTER;
                break;
            }
            //取得字符串的字节长度。
            int nLen = strlen(pszText);
            int nBufLen  = nLen + 1;
            // 为转换后的宽字符串申请内存地址。
            pwszBuffer = new wchar_t[nBufLen];
            CC_BREAK_IF(! pwszBuffer);
            //内存数据置零
            memset(pwszBuffer, 0, sizeof(wchar_t)*nBufLen);
            // 将多字节转宽字符串。
            nLen = MultiByteToWideChar(CP_UTF8, 0, pszText, nLen, pwszBuffer, nBufLen);
            //取得写字符串占据的图像区域大小
            SIZE newSize = sizeWithText(pwszBuffer, nLen, dwFmt, tSize.cx);
            //建立RECT变量做为实际绘制占据的图像区域大小
            RECT rcText = {0};
            // 如果宽度为0,则使用显示字符串所需的图像大小。
            if (tSize.cx <= 0)
            {
                tSize = newSize;
                rcText.right  = newSize.cx;
                rcText.bottom = newSize.cy;
            }
            else
            {
                LONG offsetX = 0;
                LONG offsetY = 0;
                rcText.right = newSize.cx; 
                //根据对齐方式计算横向偏移。
                if (1 != dwHoriFlag          	
                    && newSize.cx < tSize.cx)   	
                {                               	
                    offsetX = (2 == dwHoriFlag) ? tSize.cx - newSize.cx     	                        : (tSize.cx - newSize.cx) / 2;                      	                }
                //如果指定矩形高度为0,则使用显示字符串所需的图像高度。
                if (tSize.cy <= 0)
                {
                    tSize.cy = newSize.cy;
                    dwFmt   |= DT_NOCLIP;
                    rcText.bottom = newSize.cy; // store the text height to rectangle
                }
                else if (tSize.cy < newSize.cy)
                {
                    // content height larger than text height need, clip text to rect
                    rcText.bottom = tSize.cy;
                }
                else
                {
                    rcText.bottom = newSize.cy; 

                    // content larger than text, need adjust vertical position
                    dwFmt |= DT_NOCLIP;

                    //根据对齐方式计算纵向偏移。
                    offsetY = (2 == dwVertFlag) ? tSize.cy - newSize.cy     // 居下                        : (3 == dwVertFlag) ? (tSize.cy - newSize.cy) / 2   // 居中                        : 0;                                                // 居上                }
                //如果需要,调整偏移。
                if (offsetX || offsetY)
                {
                    OffsetRect(&rcText, offsetX, offsetY);
                }
            }
            //创建相应大小的位图。
            CC_BREAK_IF(! prepareBitmap(tSize.cx, tSize.cy));
            // 使用当前字体和位图
            HGDIOBJ hOldFont = SelectObject(m_hDC, m_hFont);
            HGDIOBJ hOldBmp  = SelectObject(m_hDC, m_hBmp);
            //设置背景透明模式和写字的颜色
            SetBkMode(m_hDC, TRANSPARENT);
            SetTextColor(m_hDC, RGB(255, 255, 255)); // white color
            //写字
            nRet = DrawTextW(m_hDC, pwszBuffer, nLen, &rcText, dwFmt);
            //DrawTextA(m_hDC, pszText, nLen, &rcText, dwFmt);
            //还原为之前使用的字体和位图
            SelectObject(m_hDC, hOldBmp);
            SelectObject(m_hDC, hOldFont);
        } while (0);
        //释放内存
        CC_SAFE_DELETE_ARRAY(pwszBuffer);
        return nRet;
    }
    //成员变量m_hDC及get接口
    CC_SYNTHESIZE_READONLY(HDC, m_hDC, DC);
    //成员变量m_hBmp及get接口
    CC_SYNTHESIZE_READONLY(HBITMAP, m_hBmp, Bitmap);
private:
    //友元类CCImage
	friend class CCImage;
	//成员变量m_hFont代表字体
	HFONT   m_hFont;
	//成员变量m_hWnd代表当前窗口句柄。
	HWND    m_hWnd;
	//成员m_curFontPath代表当前字体ttf文件全路径。
    std::string m_curFontPath;
};
//取得单例BitmapDC
static BitmapDC& sharedBitmapDC()
{
    static BitmapDC s_BmpDC;
    return s_BmpDC;
}
//CCImage的成员函数,使用相应的字体写字符串生成图像数据。
//参1:字符串
//参2:要创建的图片宽度,如果填0,则按照字符串的宽度进行设置。
//参3:要创建的图片高度,如果填0,则按照字符串的高度进行设置。
//参4:文字的对齐方式。
//参5:字体名称
//参6:字体大小
bool CCImage::initWithString(
                               const char *    pText, 
                               int             nWidth/* = 0*/, 
                               int             nHeight/* = 0*/,
                               ETextAlign      eAlignMask/* = kAlignCenter*/,
                               const char *    pFontName/* = nil*/,
                               int             nSize/* = 0*/)
{
    bool bRet = false;
    unsigned char * pImageData = 0;
    do 
    {
        CC_BREAK_IF(! pText);       
	//取得单例BitmapDC
        BitmapDC& dc = sharedBitmapDC();
	//设置使用的字体和大小
        if (! dc.setFont(pFontName, nSize))
        {
            CCLog("Can't found font(%s), use system default", pFontName);
        }

        // 写字
        SIZE size = {nWidth, nHeight};
        CC_BREAK_IF(! dc.drawText(pText, size, eAlignMask));

        //申请图像大小的内存,成功申请后将其地址返回给指针pImageData。
        pImageData = new unsigned char[size.cx * size.cy * 4];
        CC_BREAK_IF(! pImageData);
        //创建位图头信息结构
        struct
        {
            BITMAPINFOHEADER bmiHeader;
            int mask[4];
        } bi = {0};
        bi.bmiHeader.biSize = sizeof(bi.bmiHeader);
        //取得写字符串的位图像素
        CC_BREAK_IF(! GetDIBits(dc.getDC(), dc.getBitmap(), 0, 0, 
            NULL, (LPBITMAPINFO)&bi, DIB_RGB_COLORS));

        m_nWidth    = (short)size.cx;
        m_nHeight   = (short)size.cy;
        m_bHasAlpha = true;
        m_bPreMulti = false;
        m_pData     = pImageData;
        pImageData  = 0;
        m_nBitsPerComponent = 8;
        // 位图像素拷到pImageData中。 
        bi.bmiHeader.biHeight = (bi.bmiHeader.biHeight > 0)
           ? - bi.bmiHeader.biHeight : bi.bmiHeader.biHeight;
        GetDIBits(dc.getDC(), dc.getBitmap(), 0, m_nHeight, m_pData, 
            (LPBITMAPINFO)&bi, DIB_RGB_COLORS);

        // 双循环遍历每个像素。取得R,G,B值组成4字节的RGBA值保存。
        COLORREF * pPixel = NULL;
        for (int y = 0; y < m_nHeight; ++y)
        {
            pPixel = (COLORREF *)m_pData + y * m_nWidth;
            for (int x = 0; x < m_nWidth; ++x)
            {
                COLORREF& clr = *pPixel;
                if (GetRValue(clr) || GetGValue(clr) || GetBValue(clr))
                {
                    clr |= 0xff000000;
                }
                ++pPixel;
            }
        }

        bRet = true;
    } while (0);

    return bRet;
}

NS_CC_END

           CCImage类的代码都解析完了,其内部并没有对png,jpg,tiff做真正的解析,只是利用第三方的库进行相应的处理。经过处理后,CCImage会被图片文件的数据读取转换为Cocos2d-x所用的图像数据,存储在m_pData中。如果我们不是使用这些图片文件,那么我们就需要自已增加接口函数进行相应的处理。比如有时候做游戏不希望使用标准的图像格式以防止别人能够很容易打开图片进行查看,或者有时候将多个图片组成一个数据包。

例如:

          我们想读取一个自定义二进制图片数据(假设为Pic格式)

          (1)我们可以先定义一个新的图片类型kFmtPic,

          (2)然后定义函数

                    bool _initWithPicData(void *pData, int nDatalen);

          在其中将自定义图片文件数据pData解析到m_pData中,并设置一些属性变量。

         (3) 然后在initWithImageData函数中加入:

         else if (kFmtPic == eFmt)
        {
            //读取自定义的图片数据。
            bRet = _initWithPicData(pData, nDataLen);
            break;
        }

          这样我们就可以让CCImage支持将我们自已定义的图像数据包加载为Cocos2d-x支持的图像类型了。

最后,祝大家双节快乐!下课~


本节用到的图片功能库参考资料:

图像解码之一——使用libjpeg解码jpeg图片 :http://www.cnblogs.com/xiaoxiaoboke/archive/2012/02/13/2349763.html

图像解码之二——使用libpng解码png图片

http://www.cnblogs.com/xiaoxiaoboke/archive/2012/02/13/2349765.html

图像解码之三——giflib解码gif图片

http://www.cnblogs.com/xiaoxiaoboke/archive/2012/02/13/2349770.html

VC下使用LibTiff处理TIFF文件:

http://wenku.baidu.com/view/63b08afaaef8941ea76e05bc.html

分类: 未分类 标签:

Unable to get OpenGL ES 'draw' functions to work (+ C99 error) with Cocos2d

2012年9月28日 没有评论

I’m trying to compile some example code from the Cocos2d for iPhone 0.99 Beginner’s Guide.:

-(void)draw
{
    if(isSelected)
    {
        [self.mySprite setOpacity:100];
        glColor4f(255 / 255.0f, 0 / 255.0f, 0 / 255.0f, 255 / 255.0f);
        glPointSize( 30.0 );
        ccDrawPoint( self.mySprite.position);
    }
    [super draw];
}

Apart from this not actually drawing a rectangular highlight on top of the sprite when isSelected = YES, the gl functions are also giving me warnings:

  • Implicit declaration of function ‘glPointSize’ is invalid in C99
  • Implicit declaration of function ‘glColor4f’ is invalid in C99

Are you using Cocos2D 2.x? Try ccDrawColor4F instead of glColor4f and ccPointSize instead of glPointSize.

Make sure to #import

You can use:
ccDrawSolidRect(<#CGPoint origin#>, <#CGPoint destination#>, <#ccColor4F color#>)

This is probably a neater way to have a highlighted rectangle than using ccDrawPoint().

Hope this helps.

分类: cocos2d, stackoverflow精选 标签:

cocos2d-x 2.0.2 创建动画的方式(根据png/swf)

2012年9月25日 没有评论

猴子原创,欢迎转载。转载请注明: 转载自Cocos2D开发网–Cocos2Dev.com,谢谢!

原文地址: http://www.cocos2dev.com/?p=292

很早以前我写过了创建动画的一些方式,那都是1.x版本的时候,现在总有人问我,说创建不了动画,代码有问题。

 

2.x之后改了一些函数,其实出错了,自己看下应该会改的。

 

我今天就把创建动画的几种方式写一下。我封装了这个类,大家导入自己工程,静态方法调用就可以了。


头文件BYAnimationTool.h

//
//  BYAnimationTool.h
//  COG
//
//  Created by Yanghui Liu on 12-7-26.
//  Copyright (c) 2012年 BoyoJoy. All rights reserved.
//

#ifndef COG_BYAnimationTool_h
#define COG_BYAnimationTool_h


#include "cocos2d.h"
USING_NS_CC;

class BYAnimationTool {
public:
    static CCActionInterval* createAnimFormSwf(const char* swfName); 
    static CCActionInterval* createAnimFormSwf(const char* swfName,float frameTime); 
    static CCActionInterval* createRFAnimFormSwf(const char* swfName); 
    static CCActionInterval* createRFAnimFormSwf(const char* swfName,float frameTime); 
    
    static CCActionInterval* createRFAnimFormPng(const char* pngName,const char* frameName,int beginFrameIndex);
};

#endif


BYAnimationTool.cpp文件


//
//  BYAnimationTool.cpp
//  COG
//
//  Created by Yanghui Liu on 12-7-26.
//  Copyright (c) 2012年 BoyoJoy. All rights reserved.
//

#include "BYAnimationTool.h"

#define kDelayPerUnit 0.08f

//将SWF转成一次性动画
CCActionInterval* BYAnimationTool::createAnimFormSwf(const char* swfName){
    return createAnimFormSwf(swfName,kDelayPerUnit);
}

//将SWF转成一次性动画
CCActionInterval* BYAnimationTool::createAnimFormSwf(const char* swfName,float frameTime){
    char str1[100] = {0};
    char str2[100] = {0};
    CCSpriteFrameCache *cache = CCSpriteFrameCache::sharedSpriteFrameCache();
    sprintf(str1, "%s.plist", swfName);
    sprintf(str2, "%s.pvr.ccz", swfName);
    cache->addSpriteFramesWithFile(str1,str2);
    CCArray* animFrames = CCArray::create();
    int i = 0;
    do {
        sprintf(str2, "%s.swf/%04d", swfName,i);
        CCSpriteFrame *frame = cache->spriteFrameByName(str2);
        if (frame) {
            animFrames->addObject(frame);
        }else {
            break;
        }
    } while (++i);
    CCAnimation *animation = CCAnimation::createWithSpriteFrames(animFrames, frameTime);
    cache->removeSpriteFramesFromFile(str1);
    return CCAnimate::create(animation);
}

//将SWF转成永久性动画
CCActionInterval* BYAnimationTool::createRFAnimFormSwf(const char* swfName){
    return createRFAnimFormSwf(swfName,kDelayPerUnit);
}

//将SWF转成永久性动画 可以自定义帧率
CCActionInterval* BYAnimationTool::createRFAnimFormSwf(const char* swfName,float frameTime){
    return CCRepeatForever::create(createAnimFormSwf(swfName,frameTime));
}

//将PNG序列图转成永久性动画
CCActionInterval* BYAnimationTool::createRFAnimFormPng(const char* pngName,const char* frameName,int beginFrameIndexe){
    char str1[100] = {0};
    char str2[100] = {0};
    CCSpriteFrameCache *cache = CCSpriteFrameCache::sharedSpriteFrameCache();
    sprintf(str1, "%s.plist", pngName);
    sprintf(str2, "%s.pvr.ccz", pngName);
    cache->addSpriteFramesWithFile(str1,str2);
    CCArray* animFrames = CCArray::create();
    int i = beginFrameIndexe;
    do {
        sprintf(str2, "%s%04d.png", frameName,i);
        CCSpriteFrame *frame = cache->spriteFrameByName(str2);
        if (frame) {
            animFrames->addObject(frame);
        }else {
            break;
        }
    } while (++i);
    CCAnimation *animation = CCAnimation::createWithSpriteFrames(animFrames, kDelayPerUnit);
    cache->removeSpriteFramesFromFile(str1);
    return CCRepeatForever::create(CCAnimate::create(animation));
}


简单写了下注释,应该看得懂的。

 

使用方法:


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

CCSprite* sprite = CCSprite::create();

sprite->setPosition(ccp(winSize.width*.5,winSize.height*.5));

addChild(sprite);

sprite->runAction(BYAnimationTool::createRFAnimFormSwf("test"));

BYAnimationTool::createRFAnimFormSwf("test") 是创建一个永久性重复播放的动画。

注意:

1、所有方法传入的文件名不要后缀。

2、png和swf是我使用了TexturePaker压缩了的。

分类: 未分类 标签:

IOS 使用域名进行Socket连接

2012年9月25日 没有评论

猴子原创,欢迎转载。转载请注明: 转载自Cocos2D开发网–Cocos2Dev.com,谢谢!


原文地址: http://www.cocos2dev.com/?p=289


项目中用到了socket连接,由于我使用的boost,而boost的建立连接是只能传IP地址的,而我服务器地址是域名,所以就要将域名转到IP。


看了下代码,其实也挺简单的。


//SERVER_ADDRESS  Server Domain

hostent* host_entry = gethostbyname(SERVER_ADDRESS);

char IPStr[64] = {0};

if(host_entry !=0){

sprintf(IPStr, "%d.%d.%d.%d",(host_entry->h_addr_list[0][0]&0x00ff),

(host_entry->h_addr_list[0][1]&0x00ff),

(host_entry->h_addr_list[0][2]&0x00ff),

(host_entry->h_addr_list[0][3]&0x00ff));

}

这样就可以了,gethostbyname(),是系统自己的库函数,可以解析域名到结构体hostent,然后自己解析下结构体就可以了。

分类: 未分类 标签:

Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)

2012年9月23日 没有评论

红孩儿的游戏编程之路

CSDN博客地址:http://blog.csdn.net/honghaier

 

红孩儿Cocos2d-X学习园地QQ群:249941957 加群写:Cocos2d-x

另本章为我的Cocos2d-x教程一书初稿。望各位看官多提建议!

        

    Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)

 

           本节所用Cocos2d-x版本:cocos2d-2.0-x-2.0.2

         
之前讲解了如何将Cocos2d-x 1.0版本嵌入MFC的窗体中的具体方法,应朋友们的要求,将Cocos2d-x
2.0
版本嵌入MFC窗体的方法也整理发布。

 

      首先,我们用VC++Cocos2d-x的目录里建立了个Unicode字符集MFC对话框程序。这里命名为Cocos2dXEditor。按照HelloWorld工程设置把包含头文件目录,库文件目录,包含库都设置好。并且画好对话框界面

如图:

Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)

 

 

    我把界面设计为三部分,左边和右边用来创建对话框面板,至于要具体显示什么根据我们的工具开发需求而定。暂时先不管。而中间放一个Picture控件,做为Cocos2d-x的显示窗口。为了生成相应的窗口控件变量,我们需要将此Picture控件的ID改成自定义的一个ID,这里我们改成IDC_COCOS2DXWIN保存。

    我们画好界面后,选中Picture控件后右键单击,在弹出菜单中找到添加变量。为Picture控件生成一个控件变量。这里命名为m_Cocos2dXWin,并点击完成。

Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)

 好,现在主对话框类里有了这么一句:

 

public:
         CStatic      m_Cocos2dXWin;

     这个变量是CStatic类型的,它只是一个最简单的CWnd派生类。并不能显示Cocos2d-x。我们需要做点工作来创建一个新的类来替代它完成显示Cocos2d-x的工作。

     在工程右键弹出菜单里找到添加一项,在其子菜单中点击

Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)

在弹出的添加类对话框中“MFC”项中的“MFC

Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)


        点击“添加”。这时会弹出“MFC类向导”对话框。我们在类名里输入“CCocos2dXWin”,并选择基类CWnd。然后点击“完成”。

Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)

        向导会为我们的工程自动加入两个文件“Cocos2dXWin.h”和“Cocos2dXWin.cpp”。这才是我们要进行Cocos2d-x显示的窗体类,它的基类是CWnd,与Picture控件有相同的基类。

        打开Cocos2dXWin.h,在CCocos2dXWin类中增加一个public成员函数声明:

//创建Cocos2dX窗口
BOOL        CreateCocos2dXWindow();

       我们另外增加一个private变量用来标记窗口是否已经进行了Cocos2d-xOpenGL窗口创建。

private:
	//是否已经初始化
	BOOL				m_bInitCocos2dX;

       在CPP文件中加入需要用到的头文件:

#include "../Classes/AppDelegate.h"
#include "cocos2d.h"

       下面来手动增加函数定义:

//创建Cocos2dX窗口
BOOL	CCocos2DXWin::CreateCocos2dXWindow()
{
	//新建一个CRect变量获取窗口的客户区大小
	CRect	tClientRect;
	GetClientRect(&tClientRect);
	//取得使用的OpenGL视窗
	CCEGLView* eglView = CCEGLView::sharedOpenGLView();
	//以指定的客户区大小创建视窗,这里我们对setFrameSize增加了参数3以传入当前控件的窗口句柄。
	eglView->setFrameSize(tClientRect.Width(),tClientRect.Height(),GetSafeHwnd());
	//调用程序的运行函数,增加参数bool型变量控制是否进行消息循环。因为MFC控件本身有自已的消息响应处理。如果不改动的话,这里就会进入死循环。
	cocos2d::CCApplication::sharedApplication()->run(false);
	//这里将变量设置为TRUE
	m_bInitCocos2dX = TRUE;
	return TRUE;
}

       我们要修改两处地方。我们先对CCApplication动个小手术,使相应参数能够发挥作用。

       找到CCApplication.cpp中的run函数做以下修改:     

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路 CSDN博客地址:http://blog.csdn.net/honghaier]  

int CCApplication::run(bool bMsgLoop)
{
    PVRFrameEnableControlWindow(false);

    // Main message loop:
    MSG msg;
    LARGE_INTEGER nFreq;
    LARGE_INTEGER nNow;
    //帧定时器取得CPU时钟频率和计数
    QueryPerformanceFrequency(&nFreq);
    QueryPerformanceCounter(&m_nLast);

    // 调用派生类的程序启动处理函数
    if (!applicationDidFinishLaunching())
    {
        return 0;
    }
      CCEGLView* pMainWnd = CCEGLView::sharedOpenGLView();
	//手动修改
     if(true == bMsgLoop)
	{
		//窗口居中显示
		mainWnd->centerWindow();
		ShowWindow(mainWnd->getHWnd(), SW_SHOW);

		while (1)
		{
			if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
			{
				// Get current time tick.
				QueryPerformanceCounter(&nNow);

				//由帧间隔来控制刷新
				if (nNow.QuadPart - m_nLast.QuadPart > m_nAnimationInterval.QuadPart)
				{
					m_nLast.QuadPart = nNow.QuadPart;
					CCDirector::sharedDirector()->mainLoop();
				}
				else
				{
					Sleep(0);
				}
				continue;
			}
			//如果收到退出消息,中断消息循环。
			if (WM_QUIT == msg.message)
			{
				// Quit message loop.
				break;
			}

			// 按键消息处理
			if (! m_hAccelTable || ! TranslateAccelerator(msg.hwnd, m_hAccelTable, &msg))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}

		return (int) msg.wParam;
	}
	return 0;
}

          修改.h函数声明与cpp保持一致,再打开CCEGLView.cpp,找到setFrameSize函数,继续手术:

void CCEGLView::setFrameSize(float width, float height,HWND hWnd)
{
        //由指定的大小和句柄创建窗体
   	Create((LPCTSTR)m_szViewName, (int)width, (int)height,hWnd);
    	//调用基类的setFrameSize函初始化整屏幕和分辨率
        CCEGLViewProtocol::setFrameSize(width, height);
}

        再找到Create函数:

//创建窗口
bool CCEGLView::Create(LPCTSTR pTitle, int w, int h, HWND hWnd)
{
	bool bRet = false;
	do 
	{
		//如果已经创建了窗体,直接返回,不允许再重复创建。
		CC_BREAK_IF(m_hWnd);
		//在这里做个判断,如果参数中的窗口句柄不为空,则使用参数句柄做为成员变量m_hWnd的值。
		if(hWnd)
		{
			m_hWnd = hWnd ;
			//新增bool变量m_bIsPopupWin,用于标记是否使用已经创建好的WINDOWS控件窗口句柄做为当前OpenGL视窗的WINDOWS窗口句柄。
            m_bIsPopupWin = false;
		}
		else
		{

			HINSTANCE hInstance = GetModuleHandle( NULL );
			WNDCLASS  wc;		// Windows Class Structure

			// Redraw On Size, And Own DC For Window.
			wc.style          = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;  
			wc.lpfnWndProc    = _WindowProc;					// WndProc Handles Messages
			wc.cbClsExtra     = 0;                              // No Extra Window Data
			wc.cbWndExtra     = 0;								// No Extra Window Data
			wc.hInstance      = hInstance;						// Set The Instance
			wc.hIcon          = LoadIcon( NULL, IDI_WINLOGO );	// Load The Default Icon
			wc.hCursor        = LoadCursor( NULL, IDC_ARROW );	// Load The Arrow Pointer
			wc.hbrBackground  = NULL;                           // No Background Required For GL
			wc.lpszMenuName   = NULL;                           // We Don't Want A Menu
			wc.lpszClassName  = kWindowClassName;               // Set The Class Name

			CC_BREAK_IF(! RegisterClass(&wc) && 1410 != GetLastError());		

			// center window position
			RECT rcDesktop;
			GetWindowRect(GetDesktopWindow(), &rcDesktop);

			   WCHAR wszBuf[50] = {0};
			MultiByteToWideChar(CP_UTF8, 0, m_szViewName, -1, wszBuf, sizeof(wszBuf));

			// create window
			m_hWnd = CreateWindowEx(
				WS_EX_APPWINDOW | WS_EX_WINDOWEDGE,	// Extended Style For The Window
				kWindowClassName,									// Class Name
				wszBuf,												// Window Title
				WS_CAPTION | WS_POPUPWINDOW | WS_MINIMIZEBOX,		// Defined Window Style
				0, 0,								                // Window Position
				0,                                                  // Window Width
				0,                                                  // Window Height
				NULL,												// No Parent Window
				NULL,												// No Menu
				hInstance,											// Instance
				NULL );
		}
		//判断窗口句柄有效
CC_BREAK_IF(! m_hWnd);
       //调整窗口大小
        resize(w, h);
		 //初始化OpenGL
        		 bRet = initGL();
       		 CC_BREAK_IF(!bRet);
        		 s_pMainWindow = this;
bRet = true;
	} while (0);

	return bRet;
}

        修改.h函数声明与cpp保持一致。这样我们就完成了对于创建窗口函数的修改。但是因为屏蔽了原窗口的创建,所以也同时屏蔽了Cocos2d-x对于窗口消息的处理。我们在类视图中找到CCocos2dXWin,在右键弹出菜单中点击属性。打开类属性编辑框。

Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)


      在WindowProc一栏中增加函数WindorProc,完成后进入CCocos2dXWinWindorProc函数,因为我们在CCEGLViewCreate函数中可以找到在注册窗口类时,其设定的窗口消息处理回调函数是WindowProc.而其实例对象是单件。故可以这样修改:

LRESULT CCocos2DXWin::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
	// 这里我们先判断一下是否初始化完成Cocos2dX再进行消息处理
	if(m_bInitCocos2dX)
	{
		CCEGLView::sharedOpenGLView()->WindowProc(message, wParam, lParam);
	}
	return CWnd::WindowProc(message, wParam, lParam);
}

       这样Cocos2dXWin所实例化的窗口就可以接收到鼠标等对于窗口的消息了。但是!我们屏幕了Cocos2d-x的消息循环,而Cocos2d-x的渲染处理函数是在消息循环中调用的。我们就算现在运行程序,也看不到帅气的小怪物。所以我们必须想办法实时的调用Cocos2d-x的渲染处理函数。对于工具来说,我们并不需要考虑太高的FPS刷新帧数,所以我们只需要使用一个定时器,并在定时触发函数中进行Cocos2d-x的渲染处理函数的调用就可以了。

       在CCocos2DXWin::CreateCocos2dXWindow()函数尾部加入一句:

      

        SetTimer(1,1,NULL);


       加入一个ID1的定时器,设置它每1毫秒响应一次,其处理函数为默认,则使用CCocos2DXWinWINDOWS消息处理函数对于WM_TIMER进行响应就可以了。

       按之前增加WindorProc函数的方式找到CCocos2DXWin的消息WM_TIMER一项,增加函数OnTimer.如图:

Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)

         在其生成的函数OnTimer中我们新写一个函数调用,则每次定时响应调用:

void CCocos2DXWin::OnTimer(UINT_PTR nIDEvent)
{
	//我们写一个renderWorld函数代表Cocos2d-x的世界渲染
	cocos2d::CCApplication::sharedApplication()->renderWorld();
	CWnd::OnTimer(nIDEvent);
}

          我们打开CCApplication.h,CCApplication类增加一个publicrenderWorld函数。

//帧循环调用渲染
	bool	renderWorld();

          在cpp中我们将消息循环中的相关处理移入进来

bool CCApplication::renderWorld()
{   
	LARGE_INTEGER nNow;
	// Get current time tick.
	QueryPerformanceCounter(&nNow);

	// If it's the time to draw next frame, draw it, else sleep a while.
	if (nNow.QuadPart - m_nLast.QuadPart > m_nAnimationInterval.QuadPart)
	{
		m_nLast.QuadPart = nNow.QuadPart;
		CCDirector::sharedDirector()->mainLoop();
		return true;
	}
	return false;
}

        OK,这样我们就可以在外部来调用Cocos2d-x的显示设备进行渲染处理了。当然,我们也不能忘了在CCocos2dXWin窗口销毁时把定时器资源进行一下释放。找到类消息WM_DESTROY新增函数OnDestroy

 

Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)


void CCocos2DXWin::OnDestroy()
{
	//在Cocos2d-x的引擎文件CCEGLView_win32.cpp中CCEGLView::release()会再次发送调用DestroyWindow,所以这里用变量m_bInitCocos2dX做下判断,避免二次销毁
	if(TRUE == m_bInitCocos2dX)
	{
		//退出将m_bInitCocos2dX设为FALSE
		m_bInitCocos2dX = FALSE;
		//释放定时器资源
		KillTimer(1);
		CWnd::OnDestroy();
	}
}

          我们知道在Cocos2d-x程序退出时先后响应WM_CLOSEWM_DESTROY。在CCEGLView::WindowProc可以看到

	case WM_CLOSE:
		CCDirector::sharedDirector()->end();
		break;

	case WM_DESTROY:
                destroyGL();
		PostQuitMessage(0);
		break;

         而CCDirectorend函数并不是立即执行停止,他设置成员变量m_bPurgeDirecotorInNextLooptrue,并在下一帧mainLoop循环时调用purgeDirector()函数进行显示设备和场景的最终释放。所以我们要想在窗口退出时释放Cocos2d-x显示设备和场景,必须做一下相关处理。理解了这一点。其实就很简单了。

          只需要在CCocos2DXWin::OnDestroy()中增加两句代码即可:

void CCocos2DXWin::OnDestroy()
{
	…
	KillTimer(1);
	//调用显示设备单件实例对象的end函数
	CCDirector::sharedDirector()->end();
	//处理一次下一帧,必须调用.
	CCDirector::sharedDirector()->mainLoop();
	CWnd::OnDestroy();
	}
}

OK,CCocos2DXWin类基本完成。现在在Cocos2dXEditorDlg.h中将

CStatic            m_Cocos2dXWin;

改为

CCocos2DXWin       m_Cocos2dXWin;

 

       并将CCocos2DXWin的头文件包含进来就可以顺利编译成功。在控件初始化(比如OnInitDialog函数)中调用一下m_Cocos2dXWin.CreateMainCoco2dWindow()。此时我们就算完成了将Cocos2d-x嵌入MFCCWnd控件的过程。不过,您需要在窗口大小变化时对这个控件重设位置及大小以及投影矩阵。

void CCocos2DXWin::OnSize(UINT nType, int cx, int cy)
{
	CWnd::OnSize(nType, cx, cy);
	// TODO: 在此处添加消息处理程序代码
	if(TRUE == m_bInitCocos2dX)
	{
		CRect	tClientRect;
		GetClientRect(&tClientRect);
		//重新设置窗口大小及投影矩阵
		CCEGLView::sharedOpenGLView()->resize(tClientRect.Width(),tClientRect.Height());
		CCDirector::sharedDirector()->reshapeProjection(CCSizeMake(tClientRect.Width(),tClientRect.Height()));
        }
}

做下总结:

     1.0相比,作者删除了对于过多的目标平台的区分。(1)只保留了WINDOWS,ANDROID,IOS三个目标平台,(2)各类更清晰紧凑了,CCEGLViewCCApplication之间的耦合性大大降低。(3)将引用改成了指针,这点需要多留心。


最后运行一下看下成果吧。

       下面是我我现在做的编辑器的截图:


       Cocos2d-X 2.0嵌入MFC的子窗体的方法(1.0姐妹篇)





分类: 未分类 标签:

Trying to set up a CCLabelTTF with an integer as part of it's string in Cocos2d-X C++

2012年9月20日 没有评论

So in Objective-C with Cocos2d, I’d use a NSMutableString with format to put a variable (score) into a string. I’d take that string and use a CCLabel to place it on the screen.

Using Cocos2D-x, I’m having trouble finding a way to get this result. A simple example would be great. Thanks!

int score = 35;
float time = 0.03;
char* name = "Michael";
char text[256];
sprintf(text,"name is %s, time is %.2f, score is %d", name, time, score);
CCLabelTTF* label = CCLabelTTF::labelWithString(text,"Arial",20);
this->addChild(label);

A simpler solution to set the string at any given time (from here). First define a macro somewhere in your code.

#define ccsf(...) CCString::createWithFormat(__VA_ARGS__)->getCString()

Then you can change the string any time like this:

m_pScoreLabel->setString(ccsf("%d pts", mCurrentScore));
分类: stackoverflow精选, unity3d 标签:

cocos2d iphone 5 4 inch display support

2012年9月20日 没有评论

I have been looking everywhere for this but with no luck.

How do I prepare my cocos2d based game for bigger 4 inch display of the iPhone 5?
My app is working but i want to enhance it for the bigger 4 inch display.
Cocos2d uses its own suffixes for retina display images. For retina display of the iPhone 4 and 4S it is image-hd.png. Is there a suffix for iPhone 5? How do I accomplish this?

Cheers.

Add it to AppDelegate:

[CCFileUtils setiPadRetinaDisplaySuffix:@"your suffix"];
[CCFileUtils setiPadSuffix:@"your suffix"];
[CCFileUtils setiPhoneFourInchDisplaySuffix:@"your suffix"];
[CCFileUtils setiPhoneRetinaDisplaySuffix:@"your suffix"];

There is no extra file suffix for iPhone 5, after all it’s only 176 pixels (88 points) wider. It’s treated like a regular Retina phone, hence cocos2d will load the -hd files.

The rest is just about positioning your images depending on the device. The simplest way is to just treat the 44 points on either side as a “dead zone” where no user input can occur and where there’s no guarantee the user can see game objects.

Update:
cocos2d 2.1 added the -widehd suffix. It was said that 2.1 final release will have the suffix renamed to -iphone5hd.

In light of future screen sizes I sould personally set and use a -568hd suffix because other phones beside iPhone 5 may have the same resolution. Naming the suffix after a specific iPhone model is a tad short-sighted to say the least.

Not sure why everyone is saying there isn’t.

The suffix is -568h for iPhone5/iPod Touch 5th (so the 4 inch retina displays).

The total list:

  • -hd (iPhone 4/4S, iPod Touch 4th)
  • -568h (iPhone 5, iPod Touch 5th)
  • -ipad (iPad 1st/2nd)
  • -ipadhd (iPad 3rd/4th)

Add this to AppDelegate with your chosen suffix:

if((UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) && ([[UIScreen mainScreen] bounds].size.height == 568)) {
    [sharedFileUtils setiPhoneRetinaDisplaySuffix: @"-your suffix"];
}

It took me awhile to figure this out, since I’m new to cocos2d. So I thought a recap might be helpful for those like me. In cocos2d 2.1, all you have to do is creating graphics for the target screen sizes and follow cocos suffix naming convention. Note that cocos’s suffix convention is not the same as iOS’s.

In my case, I have a background image that occupies the full screen. So, I made…

  1. background.png at 480×320 for iPhone
  2. background-hd.png at 960×640 for iPhone retina (3.5″)
  3. background-iphone5hd.png for iPhone5 retina (4″)

And use the following code to load the image into CCSprite. Cocos will figure out which image to use for you.

CCSprite *background = [CCSprite spriteWithFile:@"background.png"];
background.position = ccp(background.textureRect.size.width/2,
background.textureRect.size.height/2);
[self addChild:background];

For an element like a character that doesn’t occupy the full screen, cocos2d will pickup character-hd.png automatically in iPhone5. There is no need to create character-iphone5hd.png version.

You can read more about this in version 2.1 release note at
https://github.com/cocos2d/cocos2d-iphone/wiki/cocos2d-v2.1-release-notes

This is how i did it for cocos2d v2.1-beta4.

In CCFileUtils.h i added:

- (void)setIphone5HDSuffix:(NSString *)suffix;

In CCFileUtils.m:

- (void)setIphone5HDSuffix:(NSString *)suffix
{
   [_suffixesDict setObject:suffix forKey:kCCFileUtilsiPhone5HD];
}

In AppDelegate.m:

[sharedFileUtils setIphone5HDSuffix:@"your_suffix"];

And that’s enough!

Did you follow the following post, adding the default image for it, named Default-568h@2x.png with a resolution of 1136×640?

How to develop or migrate apps for iPhone 5 screen resolution?

If it does not work, I found this post on cocos2d forum, containing a lot of infos:

iPhone 5 1136 x 640 screen resolution: http://www.cocos2d-iphone.org/forum/topic/39491

Now cocos2d support iPhone wide screen also.

 -wide.png for iphone 5
 -widehd.png for iPhone 5 HD

I was just playing around with suffixes in Cocos2D 2.1-rc1 and was able to get it to automatically load a iPhone5 file with the “-iphone5hd” suffix, not changing anything in AppDelegate in the sharedFileUtil section of code. Hope that helps, also.

分类: stackoverflow精选, unity3d 标签:

CCLabelTTF supported fonts?

2012年9月19日 没有评论

Where can I find a list of supported fonts for CCLabelTTF?
If I try setting the font of a CCLabel to a font like Bank Gothic xcode says that the font was unable to load. If you go to Edit>Format>Font>Show Fonts in xcode it gives you a list of the fonts in xcode and Bank Gothic is one of them so why wont it work with CCLabelTTF?

Thanks!

-Magnus

Included fonts are :

"American Typewriter"
"Arial"
"Arial Rounded MT Bold"
"Courier New"
"Georgia"
"Helvetica"
"Marker Felt"
"Times New Roman"
"Trebuchet MS"
"Verdana"
"Zapfino"

But you can add fonts very simply: Using custom fonts in Cocos2d

CCLabelTTF don’t “support” any font. It just can render text with any font, that is available on the device. So, your mac has this font, but maybe, there is no such font on the iOS device. Anyway, you can add any font to the app as resource. Just don’t forget to add it to the “Fonts provided by application” section of your info.plist.

分类: cocos2d, stackoverflow精选 标签:

Unity3D游戏制作(四)——Asset Server搭建

2012年9月14日 没有评论

本系列文章由 Amazonzx 编写,欢迎转载,转载请注明出处。

http://blog.csdn.net/amazonzx/article/details/7980117


Asset Server是目前Unity内部自带的资源版本管理工具,类似于我们平时所熟知的SVNperForce,但对于目前的UnityAsset
Server
要比SVNperForce等版本控制软件要好用一些,因为Unity3.x版本对于SVN等软件的支持并不是很好,在多人协同工作时,经常会发生数据丢失等情况。因此,本文重点介绍一下Asser
Server
的搭建方法。至于日常用法,其实与SVN等软件的用法非常相似,所以在这里就不多讲了。

关于Asset Server的搭建步骤,其实官网论坛上已经有了解释得比较详细明了,在这里,我只将其归纳总结一下,使其更加清晰明了。Asset Server的搭建步骤如下所示:

 

1、  首先去官网下载Asset Server工具,地址为:http://unity3d.com/unity/team/assetserver/。下好后在你的服务器上进行安装,这里有个重要的地方,即Asset
Server
的密码设置,界面如下,这个密码即为以后Asset Server的管理员密码。

Unity3D游戏制作(四)——Asset Server搭建


2、 安装好后,运行Unity,通过“Window->Asset Server”打开Asset Server界面,如下所示:

Unity3D游戏制作(四)——Asset Server搭建


3、 点击上图中的“Administration”,可打开如下界面:

Unity3D游戏制作(四)——Asset Server搭建


4、 Server Address输入“localhost”,User Name输入“admin”,然后输入安装Asset Server时设置的密码,即可进行连接,连接成功后,右侧“Admin Actions”中的前两项会被点亮,如下所示:

Unity3D游戏制作(四)——Asset Server搭建


5、 点击“Create”即可在服务器端建立起一个新的项目,再点击“New User”可对于该项目配置用户。点击“Create”后,会让你输入新的项目名称,假设为“AA”,界面如下:

Unity3D游戏制作(四)——Asset Server搭建


6、 点击“Create Project”即可建立起一个名为“AA”新的空项目。点击项目AA,你会发现“Admin Actions”中的按钮全部变亮,即你可以对项目进行拷贝、删除等操作。

Unity3D游戏制作(四)——Asset Server搭建


7、 在“User”中只有“admin”管理员用户,要想创建其他用户,可点击“New User”来建立。

Unity3D游戏制作(四)——Asset Server搭建


8、 填好信息后,即可创建新用户,如下图所示:

Unity3D游戏制作(四)——Asset Server搭建


9、 以上操作则在服务器上建立了一个新的空项目,以及新的用户,下面就介绍如何将已有项目资源导入到该空项目中。右键点击项目AA,选择“Connection”,即会出现如下界面:

Unity3D游戏制作(四)——Asset Server搭建


10、输入Server IP,服务器则输入“localhost”,用户名和密码,点击“Show Projects”则可看到当前服务器上的项目名称,选择你想连接的项目,即可进行连接,如下图所示:

Unity3D游戏制作(四)——Asset Server搭建


11、点击“Connect”,经过一段时间等待以后,即会出现如下界面:

Unity3D游戏制作(四)——Asset Server搭建


12、右侧的后缀为“绿色New”的资源是表明该资源对于服务器来说是新的(因为当前服务器项目为空,所以所有资源全是New),需要进行Commint。点击“Commit”,则出现如下界面:

Unity3D游戏制作(四)——Asset Server搭建


13、点击Add All后,即可进行Commit上传资源了,自此,服务器上的Asset Server搭建完毕,其他用户可以通过创建新用户来进行资源的下载和上传。

Unity3D游戏制作(四)——Asset Server搭建


分类: 未分类 标签:

Unity3D游戏制作(三)——移动平台上的角色阴影制作

2012年9月13日 没有评论

本系列文章由 Amazonzx 编写,欢迎转载,转载请注明出处。

http://blog.csdn.net/amazonzx/article/details/7973740

 

本文将重点介绍两种目前在移动平台上的主流阴影制作技术,同时也会简单介绍两种移动平台上相对较为高级的动态阴影生成方法。

由于目前主流使用Unity3.x在移动平台上并不支持阴影的动态生成技术,所以目前最普遍流行同时性价比也最高的阴影生成方法有以下两种:

 

1、  简单贴图法

所谓简单贴图法即是直接在角色的角底附加一个阴影半透明贴图,并让其跟随角色一起运动,一般是将该阴影Object成为角色模型的子物体,该种阴影生成效果如下:

Unity3D游戏制作(三)——移动平台上的角色阴影制作

 

阴影的Inspector视图如下:

Unity3D游戏制作(三)——移动平台上的角色阴影制作

 

其中ShadowComponent是控制阴影的一个脚本,与其生成无关,故不再这里介绍。影响该应用生成的主要有两个部分,一个是Shadow网格,另外一个则是渲染所需要的材质“No Name”。需要注意的是,Shadow网格是一个平面,但不建议使用Unity自身生成的Plane,因为Unity生成的面数较多,可通过3DMax等建模工具来自行建模,如下所示:

Unity3D游戏制作(三)——移动平台上的角色阴影制作

 

对于材质,最重要的则是Shader的书写,我将其列在下方,以方便大家使用:

 

Shader "iPhone/SimpleShadow"
{
	Properties
	{
		_MainTex ("MainTex", 2D) = "" {}
	}

	SubShader
	{
		Tags { "Queue" = "Transparent" }

		Pass
		{
			Blend SrcAlpha OneMinusSrcAlpha
			
			Color [_clrBase]
			
			Cull Off
			Lighting Off
			SetTexture [_MainTex] { combine texture, one - texture }
		}
	}
}

通过以上设置即可生成最简单的阴影效果,大家可以通过自己设定脚本来控制阴影的移动和变化等等。

 

但是,该阴影生成方法有一个明显的“硬伤”,即该阴影只能适用于平坦的地面,一旦地面凹凸不平或有遮挡物,则会出现“穿帮”的效果,如下图所示,该方法生成的阴影对脚下的正方体完全没有影响,所以为了解决这种问题,投影生成法应运而生。

Unity3D游戏制作(三)——移动平台上的角色阴影制作 

 

2、  投影生成法

该方法本质上来说是一种贴花(Decal)技术,即设定一个投影器,然后将阴影贴图投射到你想展现阴影的地方,该方法的优点在于投影效果不取决于被投影区域的几何形状,即被投影区域可以任意凹凸的曲面,也可以处理各种障碍物。该方法生成的阴影效果如下:

Unity3D游戏制作(三)——移动平台上的角色阴影制作

 

下面我就具体介绍一下该阴影的生成方法:

(1)          
通过“GameObject->Create Empty”来创建一个空的物体,并取名为“Shadow Projector”。

 

(2)          
通过“Component->Effects->Projector”在该空物体上加入Projector组件,并通过平移、旋转和调整参数达到如下效果:

Unity3D游戏制作(三)——移动平台上的角色阴影制作 Unity3D游戏制作(三)——移动平台上的角色阴影制作

 

(3)          
然后在Material选项中拖入已经准备好的材质,即可投影出阴影,效果如下:

Unity3D游戏制作(三)——移动平台上的角色阴影制作

 

我们看到,场景中不仅生成了阴影,同时角色的身体也“变黑”了,这是因为投影器的“Ignore Layers”设定为“Nothing”的缘故,我们将可忽略层设为角色的层“Player”,则可使角色不再被投影,效果如下:

Unity3D游戏制作(三)——移动平台上的角色阴影制作

 

(4)          
最终的Shadow ProjectorInspector视图如下:

Unity3D游戏制作(三)——移动平台上的角色阴影制作

 

其中材质所用到的shader为:

Shader "Projector/Multiply" {
   Properties {
      _ShadowTex ("Cookie", 2D) = "gray" { TexGen ObjectLinear }
      _FalloffTex ("FallOff", 2D) = "white" { TexGen ObjectLinear   }
   }

   Subshader {
      Tags { "RenderType"="Transparent-1" }
      Pass {
         ZWrite Off
         Fog { Color (1, 1, 1) }
         AlphaTest Greater 0
         ColorMask RGB
         Blend DstColor Zero
		 Offset -1, -1
         SetTexture [_ShadowTex] {
            combine texture, ONE - texture
            Matrix [_Projector]
         }
         SetTexture [_FalloffTex] {
            constantColor (1,1,1,0)
            combine previous lerp (texture) constant
            Matrix [_ProjectorClip]
         }
      }
   }
}

 

通过以上的步骤,我们即可实现投影式的阴影生成方法。与第一种方法一样,可以设定一些特定脚本来控制该阴影的移动以及变化等等。另外,需要注意的一点是“Ignore Layers”选项的设定,原则上是尽量去掉那些不需要被投影的层,从而来减少不必要的计算量。

上述两种方法是目前移动平台上的主流阴影生成方法,优点是生成简单,使用方便、计算量较小,但缺点同样突出,即该阴影是假的,并不是真的物体投射阴影,所以真实感并不强。下面我就介绍两种目前可以在移动平台上使用的实时动态阴影生成方法,不过在这篇中我们只介绍阴影效果,并不介绍具体的实现技术和生成方法,留待以后高级教程中讲解。

 

3、 Shadow Map方法

虽然Unity目前并不支持在移动设备上生成动态阴影,但同样可以通过RenderTexture来生成简单的Shadow Map,效果图如下所示:

Unity3D游戏制作(三)——移动平台上的角色阴影制作

 

我们看到所生成的阴影存在锯齿,这是由于Shadow Map分辨率不够所致,你可以通过增加RenderTexture的分辨率来减少锯齿的影响,如下图所示,当然这样做的同时也会带来一定的渲染消耗。

Unity3D游戏制作(三)——移动平台上的角色阴影制作

 

4、  环境遮挡方法

该方法取自于游戏《Shadow Gun》,通过分析物体的近似环境遮挡信息来实时生成动态阴影,效果如下图所示:

Unity3D游戏制作(三)——移动平台上的角色阴影制作

       

其生成方法大致为如下三步:

首先在角色脚底生成一个显示阴影的网格。

Unity3D游戏制作(三)——移动平台上的角色阴影制作

 

其次,根据分别在角色的跨步以及两腿处生成三个圆球,通过这三个圆球来计算底面的环境遮挡(Ambient Occlusion)信息。

Unity3D游戏制作(三)——移动平台上的角色阴影制作

 

最后,根据计算所得AO信息来动态细分网格,这样就可以生成最终的AO阴影。

Unity3D游戏制作(三)——移动平台上的角色阴影制作

 

小结

综上所述,本文已经给出了移动平台上角色阴影的两种基本渲染方法及其Shader实现,在这里我并没有去分析每种渲染效果的原理,而仅是从实际出发,直接给出对应的简单实现方法。如果想要对阴影的生成方法进行深入理解,可以Google搜索其原理进行了解。对于后两种真实的动态阴影生成方法,我将在后续的blog中进行详细更新。

分类: 未分类 标签: