porridgechen890的笔记

梦里不知身是客

官网文档

环境要求

Xcode的版本要大于等于12。iOS deployment target要大于等于9.0。

通过CocoaPods下载SDK

先添加 pod 'AppLovinSDK' 到Podfile。如果要集成其他的平台,在在这里通过勾选来生成。

再在终端里执行 pod install --repo-update

阅读全文 »

查看ruby的版本
ruby -v

查看gem的版本
gem -v

查看gem的源
gem sources -l

通过gem下载cocoapods
sudo gem install cocoapods
我尝试过不加sudo报错如下:

ERROR:  While executing gem ... (Gem::FilePermissionError)
    You don't have write permissions for the /Library/Ruby/Gems/2.6.0 directory.

网上说下载完成后还要执行pod setup。我执行了一下,瞬间完成。

官网文档

生成并添加配置文件

  1. 在 Firebase 控制台里创建项目,并填入 iOS 项目的 Bundle Identifier
  2. 下载 GoogleService-Info.plist 文件,在 Xcode 中以 Create groups 的方式添加该文件。

同步库文件

  1. xxx.xcodeproj文件所在的目录下打开终端,使用 pod init 命令生成 Podfile 文件。
  2. 打开 Podfile ,写入如下内容:
    platform :ios, '10.0'
    target 'xxx-mobile' do
    pod 'Firebase/Analytics'
    pod 'Firebase/Auth'
    pod 'Firebase/Firestore'
    end
  3. 使用 pod install --repo-update 命令下载第三方库。

初始化Firebase

  1. 下载第三方库成功后,打开 xxx.xcworkspace 文件。
  2. AppController.mm 文件里包含头文件 #import <Firebase.h>
  3. didFinishLaunchingWithOptions 函数中添加初始化的代码 [FIRApp configure]

运行报错

我遇到了这样一个错误Undefined symbol: _OBJC_CLASS_$_FIRApp

在执行 pod updatepod install 的时候,需要留意下面的那些提示,比如:

[!] The `work_crush_bonbons-mobile [Release]` target overrides the `GCC_PREPROCESSOR_DEFINITIONS` build setting defined in `Pods/Target Support Files/Pods-work_crush_bonbons-mobile/Pods-work_crush_bonbons-mobile.release.xcconfig'. This can lead to problems with the CocoaPods installation
    - Use the `$(inherited)` flag, or
    - Remove the build settings from the target.

然后我就按照它的提示,去找到 GCC_PREPROCESSOR_DEFINITIONS 这个设置,然后加上 $(inherited) ,再次执行 pod updatepod install 就可以了。

为什么需要适配

因为设计分辨率和实际分辨率不一致。

适配策略

cocos2dx提供了下面5种适配策略

  • 设计分辨率的宽高比 = 实际分辨率的宽高比

    • 只需要放大或者缩小
  • 设计分辨率的宽高比 ≠ 实际分辨率的宽高

    • 变形缩放
      • 拉伸变形,能保证宽和高都撑满,对应 EXACT_FIT
    • 不变形缩放
      • 一直缩放,直到宽和高都撑满,不留黑边,对应 NO_BORDER
      • 宽或高刚撑满就不缩放了,另一个方向就会留黑边,对应 SHOW_ALL
      • 宽缩放到撑满就停,不管高撑满没有,对应 FIXED_WIDTH
      • 高缩放到撑满就停,不管宽撑满没有,对应 FIXED_HEIGHT

推荐做法

如果是竖屏游戏,可以用定宽策略,只需要把素材做的高一点。

如果是横屏游戏,可以用定高策略,只需要把素材做的宽一点。

给美术出图的建议

目前我遇到的设备最大的长宽比是2.3。如果一个竖屏游戏的设计分辨率是 480*800 ,那么背景图的高应该是 480*2.3=1104

程序如何在代码中动态调整UI元素的位置

我推荐的做法是中心点对齐。然后四个边的某些UI元素需要动态设置位置。

如果是竖屏定宽模式,那么顶部和底部的元素需要调整位置,利用可视区域原点可视区域尺寸来做修改。

关于刘海屏

现在安卓和苹果都有刘海屏,刘海屏需要在代码中获取刘海高度来做动态调整。

  1. 打开TexturePacker。我目前用的版本是 5.2.0。

  2. 数据格式选择cocos2d-x

  3. 拖入要打包的图

  4. 选择纹理格式

  5. 点击发布精灵表,选择保存路径,写上保存为的文件名。

官方文档

  • 微信开放平台

  • 本文的介绍的是cocos2dx安卓平台接入微信分享功能(PS:微信登陆功能要给钱才能有)。参考的文档在“资源中心”->“移动应用”->“接入指南”->“Android接入指南”。

创建应用

登录微信开放平台后,“管理中心”->“移动应用”->“创建移动应用”。

创建应用的过程还挺繁琐的,需要手持身份证的照片,还需要APP的运行流程图。我记得以前没这么复杂的。

还有就是未上架的应用在使用登陆、分享等功能的时候,都有1天100次这样的限制。

假设你创建好了应用并且审核通过了,就会得到AppID和AppSecret。

“分享到朋友圈”和“发送给朋友”这两个功能是默认给你使用的。

但是“微信登录”、“微信支付”等功能是要认证过的开发者才有的。那么认证开发者需要什么呢?钱。

代码里要做的

  1. 用 Android Studio 打开工程,在模块级的 build.gradle 文件中添加微信所需的依赖项
    implementation 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:+'

    官方文档上写的 + ,有时候同步不下来,在 External Libraries 那里没看到微信的包。
    然后参考了这篇文章
    也就是去这里wechat-sdk 就能看到最新的版本号。然后把加号改成版本号就行了。我这会儿用的是 6.6.23

  2. 在 AndroidManifest.xml 文件里需要申请权限。

  3. 在程序启动时注册到微信。大概是这个样子:

    public class AppActivity extends Cocos2dxActivity {
    
        // APP_ID 替换为你的应用从官方网站申请到的合法appID
        private static final String APP_ID = "你的APP ID";
        // IWXAPI 是第三方app和微信通信的openApi接口
        public static IWXAPI api;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            // DO OTHER INITIALIZATION BELOW
            regToWx();
        }
        public void regToWx() {
            // 通过WXAPIFactory工厂,获取IWXAPI的实例
            api = WXAPIFactory.createWXAPI(this, APP_ID, false);
            // 将应用的appId注册到微信
            api.registerApp(APP_ID);
        }
    }
  4. 发送文本给朋友的示例

    public class AppActivity extends Cocos2dxActivity {
    
        public static void share_text_to_friend(final String s) {
            //初始化一个 WXTextObject 对象,填写分享的文本内容
            WXTextObject textObj = new WXTextObject();
            textObj.text = s;
    
            //用 WXTextObject 对象初始化一个 WXMediaMessage 对象
            WXMediaMessage msg = new WXMediaMessage();
            msg.mediaObject = textObj;
            msg.description = s;
    
            SendMessageToWX.Req req = new SendMessageToWX.Req();
            req.transaction = String.valueOf(System.currentTimeMillis());  //transaction字段用与唯一标示一个请求
            req.message = msg;
            req.scene = WXSceneSession;
    
            //调用api接口,发送数据到微信
            api.sendReq(req);
        }
    }
  5. 到这里,应该能拉起微信分享了。如果提示“签名不对,请检查签名是否与开放平台上填写的一致。”
    在“资源中心”->“资源下载”->“Android资源下载”->“签名生成工具”。
    用这个apk输入你的报名,用得到的字符串,更新应用签名。

苹果新推行的隐私政策

即将实行的 AppTrackingTransparency 要求 - 新闻 - Apple Developer

背景知识

  • 什么是IDFA
    IDFA全称为 Identity for Advertisers ,即广告标识符。用来标记用户,目前最广泛的用途是用于投放广告、个性化推荐等。
  • 怎么获取IDFA
    在 iOS13 及以前,系统会默认为用户开启允许追踪设置,我们可以简单的通过代码来获取到用户的 IDFA 标识符。
    if ([[ASIdentifierManager sharedManager] isAdvertisingTrackingEnabled]) {
    	NSString *idfaString = [[ASIdentifierManager sharedManager] advertisingIdentifier].UUIDString;
    	NSLog(@"%@", idfaString);
    }
    但是在 iOS14 中,这个判断用户是否允许被追踪的方法已经废弃。
    iOS14 中,系统会默认为用户关闭广告追踪权限。

iOS14怎么获取IDFA

接着使用 AppTrackingTransparency 框架中的 ATTrackingManager 中的 requestTrackingAuthorizationWithCompletionHandler 请求用户权限,在用户授权后再去访问 IDFA 才能够获取到正确信息。

  • 首先需要在Info.plist配置权限申请的文案
    <key>NSUserTrackingUsageDescription</key>
    <string>App would like to access IDFA for tracking purpose.</string>
  • 然后检查状态、弹出询问框的代码如下
    #import <AppTrackingTransparency/AppTrackingTransparency.h>
    #import <AdSupport/AdSupport.h>
    if ( @available( iOS 14, * ) )
    {
        NSUInteger status = ATTrackingManager.trackingAuthorizationStatus;
        if ( status == ATTrackingManagerAuthorizationStatusNotDetermined )
        {
            NSLog( @"未决定" );
            [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
                if ( status == ATTrackingManagerAuthorizationStatusAuthorized )
                {
                    NSLog (@"用户同意了");
                    NSString *idfaString = [[ASIdentifierManager sharedManager] advertisingIdentifier].UUIDString;
                    NSLog (@"idfaString=%@", idfaString);
                } else {
                    NSLog (@"用户没同意");
                }
            }];
        } else if ( status == ATTrackingManagerAuthorizationStatusRestricted )
        {
            NSLog (@"限制");
        } else if ( status == ATTrackingManagerAuthorizationStatusDenied )
        {
            NSLog (@"拒绝");
        } else if ( status == ATTrackingManagerAuthorizationStatusAuthorized )
        {
            NSLog (@"同意");
            NSString *idfaString = [[ASIdentifierManager sharedManager] advertisingIdentifier].UUIDString;
            NSLog (@"idfaString=%@", idfaString);
        }
    } else {
        if ( [[ASIdentifierManager sharedManager] isAdvertisingTrackingEnabled] )
        {
            NSString *idfaString = [[ASIdentifierManager sharedManager] advertisingIdentifier].UUIDString;
            NSLog( @"idfaString=%@", idfaString );
        }
    }

可能出现的问题和解决办法

  • AppTrackingTransparency IOS14 以下崩溃
    Xcode 导入库时候AppTrackingTransparency.framework时在 Build Phases -> Link Binary With Libraries 中找到 AppTrackingTransparency.framework 状态设置为 Optional(Optional 意思为如果 IOS系统支持就会加载)。

参考的文章

https://www.jianshu.com/p/877101620fdb
https://www.jianshu.com/p/1803bd950b90
https://www.jianshu.com/p/c80e291362be

数据类的头文件

#ifndef __CANDY_PHOTO_COLLECT_H__
#define __CANDY_PHOTO_COLLECT_H__

#include <string>
#include <vector>
#include <map>

#define     PHOTO_COLLECT_INVALID_PHOTO_ID 0                                        //无效的photo id
#define     PHOTO_COLLECT_PIECE_COUNT 16                                            //一张photo有多少个piece
#define     PHOTO_COLLECT_CONFIG_PATH "config/photo_collect_config.json"            //配置文件的路径
#define     PHOTO_COLLECT_PROGRESS_FILENAME "photo_collect_progress.json"           //进度文件的名字,进度文件存在可读写目录下
#define     PHOTO_COLLECT_ERROR_LOG cocos2d::log("photo_collect_error %s %s %d", __FILE__, __FUNCTION__, __LINE__)      //这个只是用来打印错误的函数名和行号,方便查错

//描述单张照片的结构体
struct StructCandyPhotoCollect
{
    //每个photo的唯一标识,这个ID必须是正整数
    int photo_id;

    //photo名
    std::string name_en;
    std::string name_zh;

    //说明
    std::string info_en;
    std::string info_zh;

    //日期
    int date_begin;
    int date_end;
    //如果一个节日有多张图,需要限制各张图在哪个关卡解锁
    int level_limit;

    //关卡
    int level_begin;

    //0表示没有解锁。解锁了就是大于0的数。(如2表示该图是第二顺位解锁的)
    int unlock_order;

    //碎片的状态:0未获得,1获得未拼好,2获得且拼好
    std::vector<int> pieces_array;

    StructCandyPhotoCollect()
    {
        photo_id = PHOTO_COLLECT_INVALID_PHOTO_ID;  //初始化为无效的photo id
        date_begin = 0;  //初始化为无效日期
        date_end = 0;  //初始化为无效日期
        level_limit = 0;  //初始化为无效关卡号
        level_begin = 0;  //初始化为无效关卡号
        unlock_order = 0;  //初始化为未解锁
        pieces_array.resize(PHOTO_COLLECT_PIECE_COUNT, 0);  //初始化为所有碎片都未得到
    }
};

// 用于管理照片收集的类
class CandyPhotoCollect
{
public:
    //构造函数
    CandyPhotoCollect();

    //读配置和进度
    void of_load_data();

    /**
    * 得碎片
    *
    * 如果发碎片成功,函数返回true,photo_id 和 piece_id 有值。
    *
    * @param game_model  游戏的类型,"001"表示主线模式,只有在主线模式下才会给玩家碎片
    * @param date_of_today  今天的日期,如3月2号就应该传入302
    * @param current_level  当前所处的关卡号
    * @param min_level  可以获得碎片的最小关卡号
    * @param photo_id 发给玩家的碎片所属的图ID
    * @param piece_index 发给玩家的碎片的索引,假如有16个碎片,索引就是从0到15
    */
    bool of_get_award_piece(const std::string& game_model, int date_of_today, int current_level, int min_level, int& photo_id, int& piece_index);

    //查:上一次得到的碎片属于哪张图,有两种情况会返回无效的photo id,(1)未解锁过任何图;(2)解锁了id为n的图,但游戏更新后,id为n的图从配置里删除了;
    int of_get_last_open_photo();

    //查:解锁了哪些图,返回的数组是按解锁顺序排列的
    std::vector<int> of_get_unlock_photos();

    //查:某一张图的所有碎片状态,返回的数组包含该图中所有碎片
    std::vector<int> of_get_pieces(int photo_id);
    //查:某一张图有多少已拼好碎片
    int of_get_photo_ok_piece_count(int photo_id);

    //查:某一张图的名字,需要传入语言类型
    std::string of_get_name(int photo_id, const std::string& language_type);
    //查:某一张图的说明,需要传入语言类型
    std::string of_get_introduce(int photo_id, const std::string& language_type);

    //改:修改某一张图的某一个碎片的状态,只有两处会调用这个方法:(1)碎片从未获得变为获得;(2)碎片从未拼好变为拼好。这两个变化都是不可逆的。
    void of_set_piece_status(int photo_id, int piece_id, int piece_status);
    //改:修改上一次获得的碎片属于哪一张图,只有一处会调用这个方法:过关后,成功找到一个碎片发给玩家
    void of_set_last_open_photo(int photo_id);
private:
    //读配置
    void of_load_config();

    //存文件,这个函数里有断言,如果在数据没有发生改变的时候去调用,会报错
    void of_save();

    //查:解锁了多少张图
    int of_get_unlock_photo_count();

    //查:某一张图有多少空闲碎片
    int of_get_photo_free_piece_count(int photo_id);
    //查:某一张图有多少已获得碎片
    int of_get_photo_got_piece_count(int photo_id);

    //查:某一张图还有哪些碎片是空闲的,返回的数组包含该图中所有空闲碎片
    std::vector<int> of_get_photo_free_pieces(int photo_id);

    //查:找出所有日期类型的配置,返回值是一个vector,内容是photo id,排在前面的是带关卡限制的日期配置,并且是按由小到大的顺序排列的
    std::vector<int> of_get_date_config();
    //查:找出所有根据关卡号来解锁图片的配置,返回值是一个map,键是关卡号,值是图ID
    std::map<int, int> of_get_level_config();

private:
    //上一次得到的碎片所属于的photo的id,如果没有得到过碎片,这个值是无效值
    int last_photo_id_;

    //所有图片的状态
    std::map<int, StructCandyPhotoCollect> photos_;
};

#endif

数据类的实现文件

#include <cassert>
#include "cocos2d.h"
#include "json/rapidjson.h"
#include "json/document.h"
#include "json/prettywriter.h"
#include "CandyPhotoCollect.h"

CandyPhotoCollect::CandyPhotoCollect()
    : last_photo_id_(PHOTO_COLLECT_INVALID_PHOTO_ID)
{
}
void CandyPhotoCollect::of_load_config()
{
    photos_.clear();
    std::map<int, int> map_level_config;

    std::string str_content = cocos2d::FileUtils::getInstance()->getStringFromFile(PHOTO_COLLECT_CONFIG_PATH);
    rapidjson::Document doc;
    doc.Parse(str_content.c_str());

    assert(doc.HasMember("type_date"));
    assert(doc["type_date"].IsArray());
    const rapidjson::Value& v1 = doc["type_date"].GetArray();
    for (rapidjson::SizeType i = 0; i < v1.Size(); i++)
    {
        const rapidjson::Value& v_item = v1[i];
        StructCandyPhotoCollect st;
        //id
        assert(v_item.HasMember("id"));
        assert(v_item["id"].IsUint());
        int photo_id = v_item["id"].GetInt();
        assert(photos_.find(photo_id) == photos_.end());
        st.photo_id = photo_id;
        //英文名
        assert(v_item.HasMember("name_en"));
        assert(v_item["name_en"].IsString());
        st.name_en = v_item["name_en"].GetString();
        //中文名
        assert(v_item.HasMember("name_zh"));
        assert(v_item["name_zh"].IsString());
        st.name_zh = v_item["name_zh"].GetString();
        //英文说明
        assert(v_item.HasMember("info_en"));
        assert(v_item["info_en"].IsString());
        st.info_en = v_item["info_en"].GetString();
        //中文说明
        assert(v_item.HasMember("info_zh"));
        assert(v_item["info_zh"].IsString());
        st.info_zh = v_item["info_zh"].GetString();
        //开始日期
        assert(v_item.HasMember("date_begin"));
        assert(v_item["date_begin"].IsNumber());
        st.date_begin = v_item["date_begin"].GetInt();
        assert(101 <= st.date_begin && st.date_begin <= 1231);
        //结束日期
        assert(v_item.HasMember("date_end"));
        assert(v_item["date_end"].IsNumber());
        st.date_end = v_item["date_end"].GetInt();
        assert(101 <= st.date_end && st.date_end <= 1231);
        assert(st.date_end >= st.date_begin);
        //关卡限制
        if (v_item.HasMember("level_limit") && v_item["level_limit"].IsNumber())
        {
            st.level_limit = v_item["level_limit"].GetInt();
        }
        //不能有 level_begin
        if (v_item.HasMember("level_begin"))
        {
            assert(false);
        }
        photos_[st.photo_id] = st;
    }

    assert(doc.HasMember("type_level"));
    assert(doc["type_level"].IsArray());
    const rapidjson::Value& v2 = doc["type_level"].GetArray();
    for (rapidjson::SizeType i = 0; i < v2.Size(); i++)
    {
        const rapidjson::Value& v_item = v2[i];
        StructCandyPhotoCollect st;
        //id
        assert(v_item.HasMember("id"));
        assert(v_item["id"].IsUint());
        int photo_id = v_item["id"].GetInt();
        assert(photos_.find(photo_id) == photos_.end());
        st.photo_id = photo_id;
        //英文名
        assert(v_item.HasMember("name_en"));
        assert(v_item["name_en"].IsString());
        st.name_en = v_item["name_en"].GetString();
        //中文名
        assert(v_item.HasMember("name_zh"));
        assert(v_item["name_zh"].IsString());
        st.name_zh = v_item["name_zh"].GetString();
        //英文说明
        assert(v_item.HasMember("info_en"));
        assert(v_item["info_en"].IsString());
        st.info_en = v_item["info_en"].GetString();
        //中文说明
        assert(v_item.HasMember("info_zh"));
        assert(v_item["info_zh"].IsString());
        st.info_zh = v_item["info_zh"].GetString();
        //解锁关卡
        assert(v_item.HasMember("level_begin"));
        assert(v_item["level_begin"].IsUint());
        st.level_begin = v_item["level_begin"].GetInt();
        assert(st.level_begin > 0);
        //不能有 date_begin 和 date_end
        if (v_item.HasMember("date_begin") || v_item.HasMember("date_end"))
        {
            assert(false);
        }
        //存入本地变量map_level_config
        assert(map_level_config.find(st.level_begin) == map_level_config.end());
        map_level_config[st.level_begin] = st.photo_id;
        //存入成员变量photos_
        photos_[st.photo_id] = st;
    }

    //配置的图片列表肯定不为空
    assert(photos_.size() > 0);
}
std::vector<int> CandyPhotoCollect::of_get_date_config()
{
    std::vector<int> ret;
    std::map<int, int> m;
    std::vector<int> v;
    for (auto i : photos_)
    {
        int date_begin = i.second.date_begin;
        if (date_begin > 0)
        {
            int level_limit = i.second.level_limit;
            if (level_limit > 0)
            {
                m[level_limit] = i.first;
            }
            else
            {
                v.push_back(i.first);
            }
        }
    }
    for (auto i : m)
    {
        ret.push_back(i.second);
    }
    for (auto i : v)
    {
        ret.push_back(i);
    }
    return ret;
}
std::map<int, int> CandyPhotoCollect::of_get_level_config()
{
    std::map<int, int> ret;
    for (auto i : photos_)
    {
        int level_begin = i.second.level_begin;
        if (level_begin > 0)
        {
            ret[level_begin] = i.first;
        }
    }
    return ret;
}
void CandyPhotoCollect::of_load_data()
{
    of_load_config();

    std::string str_progress_file_path = cocos2d::FileUtils::getInstance()->getWritablePath() + PHOTO_COLLECT_PROGRESS_FILENAME;
    std::string str_content = cocos2d::FileUtils::getInstance()->getStringFromFile(str_progress_file_path);
    if (str_content.empty())
    {
        //这里返回就代表没有读进度。此时last_photo_id_的值等于PHOTO_COLLECT_INVALID_PHOTO_ID
        return;
    }

    rapidjson::Document doc;
    doc.Parse(str_content.c_str());

    //1. 读解锁顺序。注意:进度里存的值不都是有效的,(假如进度中存的是:"unlock_order":[19,11,33],但更新游戏后id为11的图被删掉了,那么11那个值就没用了,会跳过)
    if (doc.HasMember("unlock_order") && doc["unlock_order"].IsArray())
    {
        int unlock_index = 1;
        const rapidjson::Value& rjv_unlock_order = doc["unlock_order"].GetArray();
        for (rapidjson::SizeType i = 0; i < rjv_unlock_order.Size(); i++)
        {
            const rapidjson::Value& rjv_item = rjv_unlock_order[i];
            if (rjv_item.IsUint())
            {
                int photo_id = rjv_item.GetInt();
                if (photos_.find(photo_id) == photos_.end())
                {
                    continue;
                }
                photos_[photo_id].unlock_order = unlock_index;
                unlock_index++;
            }
        }
    }

    //2. 读最近解锁的哪张图
    if (doc.HasMember("last_piece_belong_to_which_photo") && doc["last_piece_belong_to_which_photo"].IsUint())
    {
        int last_photo_id = doc["last_piece_belong_to_which_photo"].GetInt();
        if (photos_.find(last_photo_id) != photos_.end())
        {
            last_photo_id_ = last_photo_id;
        }
    }

    //3. 读每张图的碎片状态
    for (auto i : photos_)
    {
        int photo_id = i.first;
        std::string str_key_pieces = "photo_" + std::to_string(photo_id) + "_pieces";
        if (doc.HasMember(str_key_pieces.c_str()) == false)
        {
            //说明更新游戏后,新增了图,进度里是没有这张图的。
            continue;
        }
        if (doc[str_key_pieces.c_str()].IsArray())
        {
            const rapidjson::Value& rjv_pieces_status = doc[str_key_pieces.c_str()].GetArray();
            if (rjv_pieces_status.Size() == PHOTO_COLLECT_PIECE_COUNT)
            {
                for (rapidjson::SizeType j = 0; j < rjv_pieces_status.Size(); j++)
                {
                    const rapidjson::Value& rjv_item = rjv_pieces_status[j];
                    if (rjv_item.IsInt())
                    {
                        int piece_status = rjv_item.GetInt();
                        if (piece_status == 0 || piece_status == 1 || piece_status == 2)
                        {
                            photos_[photo_id].pieces_array[j] = piece_status;//把碎片的状态从文件里写进内存里
                        }
                    }
                }
            }
        }
    }

    //校验,如果last photo id是一个有效值的话,那么它对应的photo的unlock order肯定是大于0的,即肯定是解锁了的。
    if (PHOTO_COLLECT_INVALID_PHOTO_ID != last_photo_id_)
    {
        if (photos_[last_photo_id_].unlock_order <= 0)
        {
            PHOTO_COLLECT_ERROR_LOG;
            last_photo_id_ = PHOTO_COLLECT_INVALID_PHOTO_ID;
        }
    }
    //校验unlcok order的值和pieces array的值是否匹配,如果解锁顺序大于0,那么碎片数组里肯定有状态大于0的碎片
    for (auto& i : photos_)
    {
        if (i.second.unlock_order > 0)
        {
            bool b = false;
            for (size_t j = 0; j < i.second.pieces_array.size(); ++j)
            {
                if (i.second.pieces_array.at(j) > 0)
                {
                    b = true;
                    break;
                }
            }
            if (!b)
            {
                PHOTO_COLLECT_ERROR_LOG;
                i.second.unlock_order = 0;
            }
        }
    }
}
void CandyPhotoCollect::of_save()
{
    if (photos_.find(last_photo_id_) == photos_.end())
    {
        PHOTO_COLLECT_ERROR_LOG;
        return;
    }

    rapidjson::Document writedoc(rapidjson::kObjectType);
    rapidjson::Document::AllocatorType& allocator = writedoc.GetAllocator();

    //1.存最近一次得到的碎片属于哪张图
    {
        rapidjson::Value rjv_last_photo_id;
        rjv_last_photo_id.SetInt(last_photo_id_);
        writedoc.AddMember("last_piece_belong_to_which_photo", rjv_last_photo_id, allocator);
    }

    //2.按先后顺序存解锁了的图
    {
        //这个数组用来存所有的解锁顺序,用来验证解锁顺序是不是从1开始递增的
        std::vector<int> vec_unlock_order;
        for (auto& i : photos_)
        {
            bool b_unlock = false;
            for (size_t j = 0; j < i.second.pieces_array.size(); ++j)
            {
                if (i.second.pieces_array.at(j) > 0)
                {
                    b_unlock = true;
                    break;
                }
            }
            if (b_unlock)
            {
                int unlock_order = i.second.unlock_order;
                if (unlock_order <= 0)
                {
                    PHOTO_COLLECT_ERROR_LOG;
                    return;
                }
                else
                {
                    vec_unlock_order.push_back(unlock_order);
                }
            }
        }
        std::sort(vec_unlock_order.begin(), vec_unlock_order.end());
        for (int i = 0; i < vec_unlock_order.size(); ++i)
        {
            if (vec_unlock_order[i] != i + 1)
            {
                PHOTO_COLLECT_ERROR_LOG;
                return;
            }
        }
        //执行到这里说明验证通过,接下来用一个map把所有解锁的图,按解锁的先后顺序存起来
        std::map<int, int> map_unlock_order;
        for (auto i : photos_)
        {
            int photo_id = i.first;
            int unlock_order = i.second.unlock_order;
            if (unlock_order > 0)
            {
                map_unlock_order[unlock_order] = photo_id;
            }
        }
        //根据刚才得到的map,把所有解锁了的图的id,按先后顺序写入文件
        rapidjson::Value rjv(rapidjson::kArrayType);
        for (auto i : map_unlock_order)
        {
            rjv.PushBack(i.second, allocator);
        }
        writedoc.AddMember("unlock_order", rjv, allocator);
    }

    //3.存碎片状态
    {
        for (auto i : photos_)
        {
            int photo_id = i.first;
            std::string str_key = "photo_" + std::to_string(photo_id) + "_pieces";
            rapidjson::Value rjv1;
            rjv1.SetString(str_key.c_str(), int(str_key.size()), allocator);

            rapidjson::Value rjv2(rapidjson::kArrayType);
            for (auto j : i.second.pieces_array)
            {
                if (j != 0 && j != 1 && j != 2)
                {
                    PHOTO_COLLECT_ERROR_LOG;
                    return;
                }
                rjv2.PushBack(j, allocator);
            }
            writedoc.AddMember(rjv1, rjv2, allocator);
        }
    }

    rapidjson::StringBuffer jsonBuffer;
    rapidjson::PrettyWriter<rapidjson::StringBuffer> jsonWriter(jsonBuffer);
    writedoc.Accept(jsonWriter);

    std::string str_going_to_write = jsonBuffer.GetString();
    std::string str_progress_path = cocos2d::FileUtils::getInstance()->getWritablePath() + PHOTO_COLLECT_PROGRESS_FILENAME;
    cocos2d::FileUtils::getInstance()->writeStringToFile(str_going_to_write, str_progress_path);
}
bool CandyPhotoCollect::of_get_award_piece(const std::string& game_model, int date_of_today, int current_level, int min_level, int& photo_id, int& piece_index)
{
    photo_id = 0;
    piece_index = 0;

    //不是主线关卡不给碎片
    if (game_model != "001")
    {
        return false;
    }

    //如果当前关卡小于“最小的给碎片关卡”,就返回false
    if (current_level < min_level)
    {
        return false;
    }

    //“最近获得的碎片的图ID”等于“无效图ID”有两种情况:1.从来没有得到过碎片;2.以前得到过某张图的碎片,后来更新游戏后,这张图对应的ID被删掉了
    if (PHOTO_COLLECT_INVALID_PHOTO_ID == last_photo_id_)
    {
        //匹配日期的图
        for (auto id : of_get_date_config())
        {
            //日期过滤一次
            if (date_of_today < photos_[id].date_begin || date_of_today > photos_[id].date_end) continue;

            //如果带关卡号限制,再用关卡号限制过滤一次
            if (0 != photos_[id].level_limit && photos_[id].level_limit > current_level) continue;

            //判断该图是不是还一个碎片都没发出去,再过滤一次
            if (of_get_photo_free_piece_count(id) != PHOTO_COLLECT_PIECE_COUNT) continue;

            //执行到这里说明找到了满足条件的图,修改返回值
            photo_id = id;
            piece_index = rand() % PHOTO_COLLECT_PIECE_COUNT;

            //修改内部变量并存盘
            last_photo_id_ = photo_id;
            photos_[photo_id].unlock_order = 1 + of_get_unlock_photo_count();
            photos_[photo_id].pieces_array[piece_index] = 1;
            of_save();
            return true;
        }
        //匹配关卡的图
        for (auto i : of_get_level_config())
        {
            //如果当前关卡小于解锁关卡,跳过
            if (current_level < i.first) continue;

            if (of_get_photo_free_piece_count(i.second) != PHOTO_COLLECT_PIECE_COUNT) continue;

            photo_id = i.second;
            piece_index = rand() % PHOTO_COLLECT_PIECE_COUNT;

            last_photo_id_ = photo_id;
            photos_[photo_id].unlock_order = 1 + of_get_unlock_photo_count();
            photos_[photo_id].pieces_array[piece_index] = 1;
            of_save();
            return true;
        }
        //没匹配上
        return false;
    }

    //执行到这里说明last_photo_id_不等于PHOTO_COLLECT_INVALID_PHOTO_ID,校验一下这个值是否合法,要是不合法,就跳过
    if (photos_.find(last_photo_id_) == photos_.end())
    {
        last_photo_id_ = PHOTO_COLLECT_INVALID_PHOTO_ID;//把这个值赋值成初始化的状态,不然永远都发不出碎片了
        return false;
    }

    //统计一下last photo已获得的碎片个数,如果个数小于PHOTO_COLLECT_PIECE_COUNT,那就发这个图的碎片出去
    if (of_get_photo_got_piece_count(last_photo_id_) < PHOTO_COLLECT_PIECE_COUNT)
    {
        //把该图中所有状态为 未发出 的碎片找出来,把它们的索引存进一个 vector 里
        auto available_pieces = of_get_photo_free_pieces(last_photo_id_);
        //随机一个碎片发出去
        int len = available_pieces.size();
        int idx = rand() % len;
        photo_id = last_photo_id_;
        piece_index = available_pieces.at(idx);
        photos_[photo_id].pieces_array[piece_index] = 1;
        //此时last_photo_id_和photos_[photo_id].unlock_order并没有发生变化
        of_save();
        return true;
    }

    //执行到这里说明,last_photo_id_的碎片发完了,要新开一张图
    for (auto id : of_get_date_config())
    {
        auto available_pieces = of_get_photo_free_pieces(id);
        if (available_pieces.size() <= 0) continue;

        bool b1 = photos_[id].date_begin <= date_of_today && date_of_today <= photos_[id].date_end;//日期是否匹配
        if (!b1) continue;

        if (0 != photos_[id].level_limit && current_level < photos_[id].level_limit) continue;

        int len = available_pieces.size();
        int idx = rand() % len;
        photo_id = id;
        piece_index = available_pieces.at(idx);

        photos_[photo_id].unlock_order = 1 + of_get_unlock_photo_count();
        last_photo_id_ = photo_id;
        photos_[photo_id].pieces_array[piece_index] = 1;
        of_save();
        return true;
    }

    //执行到这里说明想要开一张日期匹配的图没找到,那就找一下关卡匹配的图
    for (auto i : of_get_level_config())
    {
        if (current_level < i.first) continue;
        int li_photo_id = i.second;
        auto available_pieces = of_get_photo_free_pieces(li_photo_id);
        if (available_pieces.size() <= 0) continue;

        int len = available_pieces.size();
        int idx = rand() % len;
        photo_id = li_photo_id;
        piece_index = available_pieces.at(idx);
        photos_[photo_id].unlock_order = 1 + of_get_unlock_photo_count();
        last_photo_id_ = photo_id;
        photos_[photo_id].pieces_array[piece_index] = 1;
        of_save();
        return true;
    }

    return false;
}

int CandyPhotoCollect::of_get_last_open_photo()
{
    if (photos_.size() <= 0)
    {
        PHOTO_COLLECT_ERROR_LOG;
        return PHOTO_COLLECT_INVALID_PHOTO_ID;
    }
    if (photos_.find(last_photo_id_) == photos_.end())
    {
        PHOTO_COLLECT_ERROR_LOG;
        return PHOTO_COLLECT_INVALID_PHOTO_ID;
    }
    return last_photo_id_;
}
void CandyPhotoCollect::of_set_last_open_photo(int photo_id)
{
    if (photos_.size() <= 0)
    {
        PHOTO_COLLECT_ERROR_LOG;
        return;
    }
    if (photos_.find(photo_id) == photos_.end())
    {
        PHOTO_COLLECT_ERROR_LOG;
        return;
    }

    last_photo_id_ = photo_id;
    of_save();
}

int CandyPhotoCollect::of_get_unlock_photo_count()
{
    if (photos_.size() <= 0)
    {
        PHOTO_COLLECT_ERROR_LOG;
        return 0;
    }

    int cnt = 0;
    for (auto i : photos_)
    {
        for (auto j : i.second.pieces_array)
        {
            if (j > 0)
            {
                cnt++;
                break;
            }
        }
    }
    return cnt;
}
std::vector<int> CandyPhotoCollect::of_get_unlock_photos()
{
    std::vector<int> ret;
    int cnt = 1;
    bool flag = false;
    do
    {
        flag = false;
        for (auto i : photos_)
        {
            if (i.second.unlock_order == cnt)
            {
                flag = true;
                ret.push_back(i.first);
                cnt++;
                break;
            }
        }
    } while (flag);
    return ret;
}

std::vector<int> CandyPhotoCollect::of_get_pieces(int photo_id)
{
    if (photos_.size() <= 0)
    {
        PHOTO_COLLECT_ERROR_LOG;
        return std::vector<int>();
    }
    if (photos_.find(photo_id) == photos_.end())
    {
        PHOTO_COLLECT_ERROR_LOG;
        return std::vector<int>();
    }

    return photos_[photo_id].pieces_array;
}
std::vector<int> CandyPhotoCollect::of_get_photo_free_pieces(int photo_id)
{
    if (photos_.size() <= 0)
    {
        PHOTO_COLLECT_ERROR_LOG;
        return std::vector<int>();
    }
    if (photos_.find(photo_id) == photos_.end())
    {
        PHOTO_COLLECT_ERROR_LOG;
        return std::vector<int>();
    }
    if (photos_[photo_id].pieces_array.size() != PHOTO_COLLECT_PIECE_COUNT)
    {
        PHOTO_COLLECT_ERROR_LOG;
        return std::vector<int>();
    }

    std::vector<int> available_pieces;
    for (int i = 0; i < PHOTO_COLLECT_PIECE_COUNT; ++i)
    {
        if (photos_[photo_id].pieces_array[i] == 0)
        {
            available_pieces.push_back(i);
        }
    }
    return available_pieces;
}
int CandyPhotoCollect::of_get_photo_free_piece_count(int photo_id)
{
    if (photos_.size() <= 0)
    {
        PHOTO_COLLECT_ERROR_LOG;
        return 0;
    }
    if (photos_.find(photo_id) == photos_.end())
    {
        PHOTO_COLLECT_ERROR_LOG;
        return 0;
    }

    int cnt = 0;
    for (auto status : photos_[photo_id].pieces_array)
    {
        if (status == 0)
        {
            cnt++;
        }
    }
    return cnt;
}
int CandyPhotoCollect::of_get_photo_got_piece_count(int photo_id)
{
    if (photos_.size() <= 0)
    {
        PHOTO_COLLECT_ERROR_LOG;
        return 0;
    }
    if (photos_.find(photo_id) == photos_.end())
    {
        PHOTO_COLLECT_ERROR_LOG;
        return 0;
    }

    int cnt = 0;
    for (auto status : photos_[photo_id].pieces_array)
    {
        if (status >= 1)
        {
            cnt++;
        }
    }
    return cnt;
}
int CandyPhotoCollect::of_get_photo_ok_piece_count(int photo_id)
{
    if (photos_.size() <= 0)
    {
        PHOTO_COLLECT_ERROR_LOG;
        return 0;
    }
    if (photos_.find(photo_id) == photos_.end())
    {
        PHOTO_COLLECT_ERROR_LOG;
        return 0;
    }

    int cnt = 0;
    for (auto status : photos_[photo_id].pieces_array)
    {
        if (status == 2)
        {
            cnt++;
        }
    }
    return cnt;
}
void CandyPhotoCollect::of_set_piece_status(int photo_id, int piece_id, int piece_status)
{
    if (photos_.size() <= 0)
    {
        PHOTO_COLLECT_ERROR_LOG;
        return;
    }
    if (photos_.find(photo_id) == photos_.end())
    {
        PHOTO_COLLECT_ERROR_LOG;
        return;
    }
    if (piece_id < 0 || piece_id >= PHOTO_COLLECT_PIECE_COUNT)
    {
        PHOTO_COLLECT_ERROR_LOG;
        return;
    }
    if (piece_status != 0 && piece_status != 1 && piece_status != 2)
    {
        PHOTO_COLLECT_ERROR_LOG;
        return;
    }

    //修改内存中的值
    photos_[photo_id].pieces_array[piece_id] = piece_status;

    //修改文件中的值
    of_save();
}

std::string CandyPhotoCollect::of_get_name(int photo_id, const std::string & language_type)
{
    if (photos_.size() <= 0)
    {
        PHOTO_COLLECT_ERROR_LOG;
        return "";
    }
    if (photos_.find(photo_id) == photos_.end())
    {
        PHOTO_COLLECT_ERROR_LOG;
        return "";
    }

    if (language_type == "zh")
    {
        return photos_[photo_id].name_zh;
    }
    return photos_[photo_id].name_en;
}
std::string CandyPhotoCollect::of_get_introduce(int photo_id, const std::string & language_type)
{
    if (photos_.size() <= 0)
    {
        PHOTO_COLLECT_ERROR_LOG;
        return "";
    }
    if (photos_.find(photo_id) == photos_.end())
    {
        PHOTO_COLLECT_ERROR_LOG;
        return "";
    }

    if (language_type == "zh")
    {
        return photos_[photo_id].info_zh;
    }
    return photos_[photo_id].info_en;
}

弹框类的头文件

class DialogPhoto : public DialogPub
{
public:
    DIALOG_MAKE_FUNC(DialogPhoto);
    CREATE_FUNC(DialogPhoto);

    DialogPhoto();

    //初始化
    void of_init_ui() override;

    //初始化左侧的列表框
    void of_init_listview();

    // 根据图片索引,显示某张图的详情
    void of_init_single_photo(int index);

    //panel_for_touch的触摸回调
    void on_touch_piece(Ref* sender, Widget::TouchEventType type);

    //切换照片
    void on_click_switch_photo(Ref* sender);

    // 关闭
    void on_close();

    //点击宝箱事件,三个宝箱,对应的index是012
    void on_touch_award_box(Ref* sender, Widget::TouchEventType type, int box_index);

    //更新宝箱按钮旁边的数字
    void refresh_award_box_label(int photo_id);

    //更新左侧列表框里的缩略图的显示
    void refresh_list_view_photo(int photo_id);

    //检查是否发奖,如果该发奖就发奖
    void check_need_award_box(int photo_id);

    //把单个的csb photo添加到指定的父节点上
    void of_add_photo_one(Widget* parent_wgt, int photo_id, float arg_scale);

    //根据photo id找到对应的图片路径
    string of_get_photo_path_by_id(int li_photo_id);
    string of_get_gray_photo_path_by_id(int li_photo_id);

    //拼好之后,亮图淡入完成后的回调
    void of_callback_all_pieces_ok(int li_photo_id);

    //拼好一个碎片后
    void of_work_after_piece_status_turn_to_ok(int li_photo_id, Vec2 piece_pos);
private:
    //底图是用美术出的图,还是程序用shader把亮图变暗
    const bool kUseShader;

    //csb根节点
    Node* csb_root;

    // 可以被触摸的碎片,即状态为已获得但没有归位的碎片
    vector<Sprite*> m_touchable_pieces;

    // 正在被触摸的碎片
    Sprite* m_touching_piece;

    //正在被触摸的碎片移动之前的位置
    Vec2 m_touching_piece_pos_before_move;

    //正在被触摸的碎片的父节点
    Node* m_touching_piece_father_node;

    //记录碎片的目的地位置
    array<Vec2, PHOTO_COLLECT_PIECE_COUNT> i_piece_position_array;

    //下拉宝箱的初始y坐标(三个宝箱都显示)
    float if_boxes_original_y_position;

    //当前在看哪张图
    int i_current_photo_display;

    //列表框里子项未选中时的缩放比例
    float i_listview_item_select_scale;

    //列表框里子项未选中时的缩放比例
    float i_listview_item_normal_scale;
};

弹框类的实现文件

#pragma region DialogPhoto

DialogPhoto::DialogPhoto()
    : kUseShader(true)
{
}

void DialogPhoto::of_init_ui() {

    string ls_csb = "csb_ui_photo/dialog_photocollect_new.csb";
    //目前只有“入”的动画,没有“出”的动画。入动画的最后一帧是58
    auto l_node_csb = PUI::of_create_dialog(this, ls_csb, 0, g.rrscale, 60);
    //目前在csb文件里没有区分中英文,所以没有调用g.of_set_ui_by_country()
    csb_root = l_node_csb;

    //给关闭按钮添加回调
    auto l_button_close = (Button*)PUI::of_find_by_name(l_node_csb, "button_close");
    l_button_close->addClickEventListener(CC_CALLBACK_0(DialogPhoto::on_close, this));

    //往列表框中加入已解锁的图,每张图的tag是它们的photo id
    of_init_listview();

    //重新设置宝箱的y坐标,宝箱节点必须贴在屏幕的上边缘
    {
        auto origin = Director::getInstance()->getVisibleOrigin();
        auto visize = Director::getInstance()->getVisibleSize();
        auto node = PUI::of_find_by_name(l_node_csb, "node_album_gift_top_pos");
        auto node_pos = node->getPosition();
        auto parent = node->getParent();
        auto world_pos = parent->convertToWorldSpace(node_pos);
        auto world_pos_1 = Vec2(world_pos.x, origin.y + visize.height);
        auto node_pos_1 = parent->convertToNodeSpace(world_pos_1);
        node->setPosition(node_pos_1);
        if_boxes_original_y_position = node_pos_1.y;
    }

    //得到每个碎片的最终位置,存进i_piece_position_array这个成员变量里,一会儿用来判断碎片“归位”
    {
        auto csb_file = CSLoader::createNode("csb_ui_photo/node_photo_one.csb");
        for (int i = 0; i < PHOTO_COLLECT_PIECE_COUNT; i++)
        {
            auto piece_node = PUI::of_find_by_name(csb_file, StringUtils::format("sp_piece%d", i));
            i_piece_position_array[i] = piece_node->getPosition();
        }
    }

    //给面板添加触摸回调
    auto panel_for_touch = (Layout*)PUI::of_find_by_name(l_node_csb, "panel_for_touch");
    panel_for_touch->removeAllChildren();
    panel_for_touch->addTouchEventListener(CC_CALLBACK_2(DialogPhoto::on_touch_piece, this));

    //给每个宝箱添加触摸回调,并隐藏每个宝箱的说明
    auto btn_box_1 = (Button*)PUI::of_find_by_name(l_node_csb, "btn_albumgiftbox_1");
    auto btn_box_2 = (Button*)PUI::of_find_by_name(l_node_csb, "btn_albumgiftbox_2");
    auto btn_box_3 = (Button*)PUI::of_find_by_name(l_node_csb, "btn_albumgiftbox_3");
    btn_box_1->addTouchEventListener(CC_CALLBACK_2(DialogPhoto::on_touch_award_box, this, 0));
    btn_box_2->addTouchEventListener(CC_CALLBACK_2(DialogPhoto::on_touch_award_box, this, 1));
    btn_box_3->addTouchEventListener(CC_CALLBACK_2(DialogPhoto::on_touch_award_box, this, 2));
    PUI::of_find_by_name(l_node_csb, "sp_albumgiftbox_info_1")->setVisible(false);
    PUI::of_find_by_name(l_node_csb, "sp_albumgiftbox_info_2")->setVisible(false);
    PUI::of_find_by_name(l_node_csb, "sp_albumgiftbox_info_3")->setVisible(false);

    //在右侧显示详情
    int li_last_open_photo = g.candy_photo_collect->of_get_last_open_photo();
    if (li_last_open_photo != PHOTO_COLLECT_INVALID_PHOTO_ID)
    {
        of_init_single_photo(li_last_open_photo);
    }
    else
    {
        of_init_single_photo(999);
    }
}

//初始化左侧列表框
void DialogPhoto::of_init_listview()
{
    auto l_lsv = (ListView*)PUI::of_find_by_name(csb_root, "listview_photos");
    l_lsv->removeAllChildren();

    //得到列表框的宽度 280
    float lf_lsv_w = l_lsv->getContentSize().width;

    //得到一张图的宽高
    float lf_photo_w = 856;
    float lf_photo_h = 900;

    //计算列表框里子项未选中时的缩放比例
    float lf_item_width = lf_lsv_w - 20;//260,没选中时的宽度
    i_listview_item_normal_scale = lf_item_width / lf_photo_w;//没选中时的缩放
    float lf_item_height = lf_photo_h * i_listview_item_normal_scale;//没选中时的高度

    //计算列表框里子项选中时的缩放比例
    i_listview_item_select_scale = lf_lsv_w / lf_item_width;

    //得到已经解锁的所有photo的id的集合
    auto l_vector_unlock_photos_id = g.candy_photo_collect->of_get_unlock_photos();

    //得到最近得到的碎片所属于的photo id
    int li_last_open_photo = g.candy_photo_collect->of_get_last_open_photo();

    for (auto i : l_vector_unlock_photos_id)
    {
        auto panel = Layout::create();
        panel->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
        panel->setContentSize(Size(lf_item_width, lf_item_height));
        panel->setTag(i);
        panel->setTouchEnabled(true);
        panel->addClickEventListener(CC_CALLBACK_1(DialogPhoto::on_click_switch_photo, this));
        l_lsv->pushBackCustomItem(panel);

        //加载csb到这个widget上
        of_add_photo_one(panel, i, i_listview_item_normal_scale);
    }

    //在列表框尾部添加“敬请期待”
    {
        auto panel = Layout::create();
        panel->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
        panel->setContentSize(Size(lf_item_width, lf_item_height));
        panel->setTag(999);
        panel->setTouchEnabled(true);
        panel->addClickEventListener(CC_CALLBACK_1(DialogPhoto::on_click_switch_photo, this));
        l_lsv->pushBackCustomItem(panel);
        auto spr = Sprite::create("photoes/coming_soon/999_normal.png");
        spr->setPosition(Vec2(lf_item_width / 2, lf_item_height / 2));
        spr->setScale(i_listview_item_normal_scale);
        panel->addChild(spr);
        string ls_name_path = g.is_country == "zh" ? "photoes/coming_soon/photo_name_999_zh.png" : "photoes/coming_soon/photo_name_999.png";
        auto spr_name = Sprite::create(ls_name_path);
        spr_name->setPosition(Vec2(367, 75));
        spr->addChild(spr_name);
        auto spr2 = Sprite::create("photoes/coming_soon/999_co.png");
        spr2->setScale(1.99f);
        spr2->setPosition(Vec2(spr->getContentSize().width / 2, spr->getContentSize().height / 2));
        spr->addChild(spr2);
    }
}

//显示单张图
void DialogPhoto::of_init_single_photo(int photo_id)
{
    m_touchable_pieces.clear();
    auto panel_for_touch = (Layout*)PUI::of_find_by_name(csb_root, "panel_for_touch");
    panel_for_touch->removeAllChildren();

    if (photo_id == 999)
    {
        auto sp = Sprite::create("photoes/coming_soon/999_normal.png");
        sp->setPosition(Vec2(panel_for_touch->getContentSize().width / 2, panel_for_touch->getContentSize().height / 2));
        panel_for_touch->addChild(sp);
        auto spr2 = Sprite::create("photoes/coming_soon/999_co.png");
        spr2->setScale(1.996f);
        spr2->setPosition(Vec2(sp->getContentSize().width / 2, sp->getContentSize().height / 2));
        sp->addChild(spr2);
        string ls_name_path = g.is_country == "zh" ? "photoes/coming_soon/photo_name_999_zh.png" : "photoes/coming_soon/photo_name_999.png";
        auto spr_name = Sprite::create(ls_name_path);
        spr_name->setPosition(Vec2(367, 75));
        sp->addChild(spr_name);
        PUI::of_find_by_name(this, "node_album_gift_top_pos")->setVisible(false);
        //当photo_id是999时,没有给i_current_photo_display赋值,也不影响
        return;
    }

    i_current_photo_display = photo_id;

    auto csb_file = CSLoader::createNode("csb_ui_photo/node_photo_one.csb");
    auto csb_act = CSLoader::createTimeline("csb_ui_photo/node_photo_one.csb");
    panel_for_touch->addChild(csb_file);
    csb_file->runAction(csb_act);
    csb_act->gotoFrameAndPlay(0, 60, false);

    //查询该图拼完没有
    int ok_piece_cnt = g.candy_photo_collect->of_get_photo_ok_piece_count(photo_id);
    if (ok_piece_cnt < PHOTO_COLLECT_PIECE_COUNT)
    {
        //显示底图
        auto l_sp_shadow_one = (Sprite*)PUI::of_find_by_name(csb_file, "sp_photo_shadow_bg");
        l_sp_shadow_one->setVisible(true);
        if (kUseShader)
        {
            PUI::of_sprite_set_texture(l_sp_shadow_one, of_get_photo_path_by_id(photo_id));
            PUI::of_node_set_gray(l_sp_shadow_one);
        }
        else
        {
            PUI::of_sprite_set_texture(l_sp_shadow_one, of_get_gray_photo_path_by_id(photo_id));
        }
        //显示碎片
        auto arr = g.candy_photo_collect->of_get_pieces(photo_id);
        for (int i = 0; i < PHOTO_COLLECT_PIECE_COUNT; i++)
        {
            auto piece_sprite = (Sprite*)PUI::of_find_by_name(csb_file, StringUtils::format("sp_piece%d", i));
            piece_sprite->setCascadeOpacityEnabled(true);
            piece_sprite->setTag(i);
            int piece_status = arr[i];
            if (piece_status == 0)
            {
                piece_sprite->setVisible(false);//未得到的碎片不显示
                continue;
            }

            auto piece_pos = piece_sprite->getPosition();
            auto piece_size = piece_sprite->getContentSize();

            auto sp_stencil = Sprite::create(piece_sprite->getTexture()->getPath());
            auto node_clip = ClippingNode::create(sp_stencil);
            node_clip->setAlphaThreshold(0);
            node_clip->setInverted(false);
            node_clip->setPosition(Vec2(piece_size.width / 2, piece_size.height / 2));
            node_clip->setCascadeOpacityEnabled(true);
            piece_sprite->addChild(node_clip);

            auto sp_content = Sprite::create(of_get_photo_path_by_id(photo_id));
            sp_content->setAnchorPoint(Vec2::ANCHOR_BOTTOM_LEFT);
            sp_content->setPosition(Vec2(10 - piece_pos.x, 130 - piece_pos.y));
            node_clip->addChild(sp_content);

            auto sp_shield = Sprite::create(StringUtils::format("photoes/ui/001_%d_1.png", i));
            sp_shield->setPosition(Vec2(piece_size.width / 2, piece_size.height / 2));
            sp_shield->setCascadeOpacityEnabled(true);
            piece_sprite->addChild(sp_shield);

            if (piece_status == 1)//没有归位的碎片,打乱位置显示
            {
                int n = m_touchable_pieces.size();
                piece_sprite->setPosition(Vec2(150 + 30 * n, 100));
                piece_sprite->setLocalZOrder(2);
                m_touchable_pieces.push_back(piece_sprite);
            }
        }
        //隐藏亮图
        auto sp_normal_one = (Sprite*)PUI::of_find_by_name(csb_file, "sp_photo_normal_bg");
        sp_normal_one->setVisible(false);
    }
    else
    {
        //隐藏底图
        auto l_sp_shadow_one = (Sprite*)PUI::of_find_by_name(csb_file, "sp_photo_shadow_bg");
        l_sp_shadow_one->setVisible(false);
        //隐藏碎片
        PUI::of_find_by_name(csb_file, "node_pieces")->setVisible(false);
        //显示亮图
        auto sp_normal_one = (Sprite*)PUI::of_find_by_name(csb_file, "sp_photo_normal_bg");
        PUI::of_sprite_set_texture(sp_normal_one, of_get_photo_path_by_id(photo_id));
        sp_normal_one->setCascadeOpacityEnabled(true);
    }

    //不显示节日名
    PUI::of_find_by_name(csb_file, "txt_name_en")->setVisible(false);
    PUI::of_find_by_name(csb_file, "txt_name_zh")->setVisible(false);

    //显示节日说明
    auto photo_text_en = (Text*)PUI::of_find_by_name(csb_file, "txt_info_en");
    auto photo_text_zh = (Text*)PUI::of_find_by_name(csb_file, "txt_info_zh");
    if (ok_piece_cnt < PHOTO_COLLECT_PIECE_COUNT)
    {
        photo_text_en->setVisible(false);
        photo_text_zh->setVisible(false);
    }
    else
    {
        photo_text_en->setString(g.candy_photo_collect->of_get_introduce(photo_id, g.is_country));
        photo_text_zh->setString(g.candy_photo_collect->of_get_introduce(photo_id, g.is_country));
        photo_text_en->setVisible(g.is_country == "zh" ? false : true);
        photo_text_zh->setVisible(g.is_country == "zh" ? true : false);
    }

    //显示宝箱上的数字
    refresh_award_box_label(photo_id);

    PUI::of_find_by_name(csb_root, "sp_albumgiftbox_info_1")->setVisible(false);
    PUI::of_find_by_name(csb_root, "sp_albumgiftbox_info_2")->setVisible(false);
    PUI::of_find_by_name(csb_root, "sp_albumgiftbox_info_3")->setVisible(false);
}

//点击碎片的回调
void DialogPhoto::on_touch_piece(Ref* sender, Widget::TouchEventType type)
{
    auto lyt = dynamic_cast<Layout*>(sender);
    if (type == Widget::TouchEventType::BEGAN)
    {
        m_touching_piece = nullptr;
        auto pos_world = lyt->getTouchBeganPosition();
        for (auto it = m_touchable_pieces.crbegin(); it != m_touchable_pieces.crend(); ++it)
        {
            auto sp_size = (*it)->getContentSize();
            auto pos_node = (*it)->convertToNodeSpace(pos_world);
            if (pos_node.x > 0
                && pos_node.x < sp_size.width
                && pos_node.y > 0
                && pos_node.y < sp_size.height)
            {
                m_touching_piece = (*it);
                m_touching_piece->setLocalZOrder(3);//正在被拖动的碎片的层级比其他碎片高
                m_touching_piece_pos_before_move = m_touching_piece->getPosition();
                m_touching_piece_father_node = m_touching_piece->getParent();
                break;
            }
        }
        if (m_touching_piece)
        {
            for (auto it = m_touchable_pieces.crbegin(); it != m_touchable_pieces.crend(); ++it)
            {
                (*it)->setVisible((*it) == m_touching_piece);
            }
        }
    }
    else if (type == Widget::TouchEventType::MOVED)
    {
        if (m_touching_piece)
        {
            auto size_touching_sp = m_touching_piece->getContentSize();
            auto touch_pos_1 = m_touching_piece_father_node->convertToNodeSpace(lyt->getTouchBeganPosition());
            auto touch_pos_2 = m_touching_piece_father_node->convertToNodeSpace(lyt->getTouchMovePosition());
            auto touch_pos_delta = touch_pos_2 - touch_pos_1;
            auto new_pos = m_touching_piece_pos_before_move + touch_pos_delta;
            bool x_can_move = (new_pos.x > 0 + size_touching_sp.width / 2 - 65) && (new_pos.x < lyt->getContentSize().width - size_touching_sp.width / 2 - 65);
            bool y_can_move = (new_pos.y > 0 + size_touching_sp.height / 2 - 18) && (new_pos.y < lyt->getContentSize().height - size_touching_sp.height / 2 - 18);
            if (x_can_move || y_can_move)//移动不能超出父容器
            {
                if (x_can_move && y_can_move)
                {
                    auto destination_position = i_piece_position_array[m_touching_piece->getTag()];
                    if (abs(new_pos.x - destination_position.x) < 8 && abs(new_pos.y - destination_position.y) < 8)//靠近目标位置就吸附上去
                    {
                        m_touching_piece->setPosition(destination_position);
                        m_touching_piece->setLocalZOrder(1);
                        //碎片归位之后,播放一个光效
                        PUI::of_create_movie_by_csb_once(m_touching_piece, "csb_ui_awardbox/node_effect_end_par.csb", m_touching_piece->getContentSize().width / 2, m_touching_piece->getContentSize().height / 2, 1.4f, 1.4f);
                        for (auto it = m_touchable_pieces.begin(); it != m_touchable_pieces.end(); ++it)//碎片归位后,把它从可移动的碎片vector里移除
                        {
                            if (*it == m_touching_piece)
                            {
                                m_touchable_pieces.erase(it);
                                break;
                            }
                        }
                        //存档,这里是更新当前显示这张图的某个碎片的状态
                        g.candy_photo_collect->of_set_piece_status(i_current_photo_display, m_touching_piece->getTag(), 2);
                        //把正在触摸的碎片置空
                        m_touching_piece = nullptr;
                        //拼好之后要做哪些事
                        of_work_after_piece_status_turn_to_ok(i_current_photo_display, destination_position);
                    }
                    else
                    {
                        m_touching_piece->setPosition(new_pos);
                    }
                }
                else if (x_can_move)
                {
                    m_touching_piece->setPositionX(new_pos.x);
                }
                else
                {
                    m_touching_piece->setPositionY(new_pos.y);
                }
            }
        }
    }
    else if (type == Widget::TouchEventType::ENDED)
    {
        m_touching_piece = nullptr;
        for (auto sp : m_touchable_pieces)
        {
            sp->setVisible(true);
            sp->setLocalZOrder(2);
        }
    }
    else if (type == Widget::TouchEventType::CANCELED)
    {
        m_touching_piece = nullptr;
        for (auto sp : m_touchable_pieces)
        {
            sp->setVisible(true);
            sp->setLocalZOrder(2);
        }
    }
}

//点击左侧的列表框,切换照片
void DialogPhoto::on_click_switch_photo(Ref * sender)
{
    auto wgt = dynamic_cast<Widget*>(sender);
    auto lsv = (ListView*)PUI::of_find_by_name(csb_root, "listview_photos");
    auto items = lsv->getItems();
    for (auto item : items)
    {
        if (wgt == item)
        {
            item->setScale(i_listview_item_select_scale);
        }
        else
        {
            item->setScale(1);
        }
    }
    int tag = wgt->getTag();//这个tag就是图片的ID
    of_init_single_photo(tag);
}

//点击关闭的回调
void DialogPhoto::on_close() {

    if (dialog_listener != nullptr) {
        dialog_listener->doDialogAction(this, DialogAction::dialog_photo_close);
    }
    of_close_dialog();
    g.play_sound("music/sound_button_clicked.mp3");
}

//点击右侧的宝箱,显示这个宝箱的奖励内容
void DialogPhoto::on_touch_award_box(Ref* sender, Widget::TouchEventType type, int box_index)
{
    if (type == Widget::TouchEventType::BEGAN)
    {
        auto n1 = PUI::of_find_by_name(this, "sp_albumgiftbox_info_1");
        auto n2 = PUI::of_find_by_name(this, "sp_albumgiftbox_info_2");
        auto n3 = PUI::of_find_by_name(this, "sp_albumgiftbox_info_3");

        auto act2 = Show::create();
        auto act3 = DelayTime::create(2.3f);
        auto act4 = Hide::create();
        auto act = Sequence::create(act2, act3, act4, nullptr);
        if (box_index == 0)
        {
            if (n1->isVisible() == false) n1->runAction(act);
            n2->setVisible(false);
            n3->setVisible(false);
        }
        else if (box_index == 1)
        {
            n1->setVisible(false);
            if (n2->isVisible() == false) n2->runAction(act);
            n3->setVisible(false);
        }
        else if (box_index == 2)
        {
            n1->setVisible(false);
            n2->setVisible(false);
            if (n3->isVisible() == false) n3->runAction(act);
        }
    }
}

//更新宝箱下面的数字显示
void DialogPhoto::refresh_award_box_label(int photo_id)
{
    auto node_boxes_parent = PUI::of_find_by_name(csb_root, "node_album_gift_top_pos");
    node_boxes_parent->setVisible(true);
    auto fnt_box_1 = (TextBMFont*)PUI::of_find_by_name(this, "fnt_albumgiftbox_1");
    auto fnt_box_2 = (TextBMFont*)PUI::of_find_by_name(this, "fnt_albumgiftbox_2");
    auto fnt_box_3 = (TextBMFont*)PUI::of_find_by_name(this, "fnt_albumgiftbox_3");
    int ok_piece_cnt = g.candy_photo_collect->of_get_photo_ok_piece_count(photo_id);
    auto box1 = PUI::of_find_by_name(node_boxes_parent, "node_albumgiftbox_1");
    auto box2 = PUI::of_find_by_name(node_boxes_parent, "node_albumgiftbox_2");
    auto box3 = PUI::of_find_by_name(node_boxes_parent, "node_albumgiftbox_3");
    if (ok_piece_cnt >= 16)
    {
        box1->setVisible(false);
        box2->setVisible(false);
        box3->setVisible(false);
        fnt_box_1->setString("3/3");
        fnt_box_2->setString("9/9");
        fnt_box_3->setString("16/16");
    }
    else if (ok_piece_cnt >= 9)
    {
        box1->setVisible(false);
        box2->setVisible(false);
        box3->setVisible(true);
        node_boxes_parent->setPositionY(if_boxes_original_y_position + 447);
        fnt_box_1->setString("3/3");
        fnt_box_2->setString("9/9");
        fnt_box_3->setString(StringUtils::format("%d/16", ok_piece_cnt));
    }
    else if (ok_piece_cnt >= 3)
    {
        box1->setVisible(false);
        box2->setVisible(true);
        box3->setVisible(true);
        node_boxes_parent->setPositionY(if_boxes_original_y_position + 224);
        fnt_box_1->setString("3/3");
        fnt_box_2->setString(StringUtils::format("%d/9", ok_piece_cnt));
        fnt_box_3->setString(StringUtils::format("%d/16", ok_piece_cnt));
    }
    else
    {
        box1->setVisible(true);
        box2->setVisible(true);
        box3->setVisible(true);
        node_boxes_parent->setPositionY(if_boxes_original_y_position);
        fnt_box_1->setString(StringUtils::format("%d/3", ok_piece_cnt));
        fnt_box_2->setString(StringUtils::format("%d/9", ok_piece_cnt));
        fnt_box_3->setString(StringUtils::format("%d/16", ok_piece_cnt));
    }
}

//更新左侧列表框里的缩略图的显示
void DialogPhoto::refresh_list_view_photo(int photo_id)
{
    if (csb_root)
    {
        auto lsv = (ListView*)PUI::of_find_by_name(csb_root, "listview_photos");
        if (lsv)
        {
            auto panel = dynamic_cast<Layout*>(lsv->getChildByTag(photo_id));
            if (panel)
            {
                of_add_photo_one(panel, photo_id, i_listview_item_normal_scale);
            }
        }
    }
}

//检查是否需要发奖
void DialogPhoto::check_need_award_box(int photo_id)
{
    int ok_piece_cnt = g.candy_photo_collect->of_get_photo_ok_piece_count(photo_id);
    if (ok_piece_cnt == 3)
    {
        g.is_message_for_dialog = "beforelineline,1;beforesame,1";
        g.cm->of_award_by_string(g.is_message_for_dialog);
        DialogAwardBox::make(this, 999, nullptr);
    }
    else if (ok_piece_cnt == 9)
    {
        g.is_message_for_dialog = "beforebombbomb,1;beforesame,1;coin_little,100";
        g.cm->of_award_by_string(g.is_message_for_dialog);
        DialogAwardBox::make(this, 999, nullptr);
    }
    else if (ok_piece_cnt == 16)
    {
        auto node_photo_one = PUI::of_find_by_name(csb_root, "node_photo_one");
        PUI::of_find_by_name(csb_root, "node_pieces")->setVisible(false);
        //拼完就把亮图显示出来,淡入
        auto sp_normal_one = (Sprite*)PUI::of_find_by_name(node_photo_one, "sp_photo_normal_bg");
        PUI::of_sprite_set_texture(sp_normal_one, of_get_photo_path_by_id(photo_id));
        sp_normal_one->setVisible(true);
        sp_normal_one->setOpacity(0);
        auto act1 = FadeIn::create(2.5f);
        auto act2 = CallFunc::create(CC_CALLBACK_0(DialogPhoto::of_callback_all_pieces_ok, this, photo_id));
        auto act = Sequence::create(act1, act2, nullptr);
        sp_normal_one->runAction(act);
    }
}

//把单个的csb photo添加到指定的父节点上
void DialogPhoto::of_add_photo_one(Widget * l_widget_parent, int li_photo_id, float lf_scale)
{
    l_widget_parent->removeAllChildren();
    auto l_node_csb = CSLoader::createNode("csb_ui_photo/node_photo_one.csb");
    l_node_csb->setScale(lf_scale);
    l_widget_parent->addChild(l_node_csb);
    auto l_timeline_action_csb = CSLoader::createTimeline("csb_ui_photo/node_photo_one.csb");
    l_node_csb->runAction(l_timeline_action_csb);
    l_timeline_action_csb->gotoFrameAndPause(65);//这个csb有一个淡入的动画,是0到60帧,这里直接显示65帧,不需要淡入动画

    //查询该图拼完没有,如果没拼完就显示暗图和碎片,如果拼完了就显示亮图
    int li_ok_piece_cnt = g.candy_photo_collect->of_get_photo_ok_piece_count(li_photo_id);
    if (li_ok_piece_cnt < PHOTO_COLLECT_PIECE_COUNT)
    {
        //显示底图
        auto l_sp_shadow_one = (Sprite*)PUI::of_find_by_name(l_node_csb, "sp_photo_shadow_bg");
        l_sp_shadow_one->setVisible(true);
        if (kUseShader)
        {
            PUI::of_sprite_set_texture(l_sp_shadow_one, of_get_photo_path_by_id(li_photo_id));
            PUI::of_node_set_gray(l_sp_shadow_one);
        }
        else
        {
            PUI::of_sprite_set_texture(l_sp_shadow_one, of_get_gray_photo_path_by_id(li_photo_id));
        }
        //显示碎片
        auto l_vector_pieces_array = g.candy_photo_collect->of_get_pieces(li_photo_id);
        for (int i = 0; i < PHOTO_COLLECT_PIECE_COUNT; i++)
        {
            auto l_sprite_piece = (Sprite*)PUI::of_find_by_name(l_node_csb, StringUtils::format("sp_piece%d", i));
            l_sprite_piece->setTag(i);
            int li_piece_status = l_vector_pieces_array[i];
            if (li_piece_status != 2)
            {
                l_sprite_piece->setVisible(false);//未归位的碎片不显示
                continue;
            }

            auto piece_pos = l_sprite_piece->getPosition();
            auto piece_size = l_sprite_piece->getContentSize();

            auto sp_stencil = Sprite::create(l_sprite_piece->getTexture()->getPath());
            auto node_clip = ClippingNode::create(sp_stencil);
            node_clip->setAlphaThreshold(0);
            node_clip->setInverted(false);
            node_clip->setPosition(Vec2(piece_size.width / 2, piece_size.height / 2));
            l_sprite_piece->addChild(node_clip);

            auto sp_content = Sprite::create(of_get_photo_path_by_id(li_photo_id));
            sp_content->setAnchorPoint(Vec2::ANCHOR_BOTTOM_LEFT);
            sp_content->setPosition(Vec2(10 - piece_pos.x, 130 - piece_pos.y));
            node_clip->addChild(sp_content);

            auto sp_shield = Sprite::create(StringUtils::format("photoes/ui/001_%d_1.png", i));
            sp_shield->setPosition(Vec2(piece_size.width / 2, piece_size.height / 2));
            l_sprite_piece->addChild(sp_shield);
        }
        //隐藏亮图
        auto sp_normal_one = (Sprite*)PUI::of_find_by_name(l_node_csb, "sp_photo_normal_bg");
        sp_normal_one->setVisible(false);
    }
    else
    {
        //隐藏底图
        auto l_sp_shadow_one = (Sprite*)PUI::of_find_by_name(l_node_csb, "sp_photo_shadow_bg");
        l_sp_shadow_one->setVisible(false);
        //隐藏碎片,把所有碎片的父节点给隐藏
        PUI::of_find_by_name(l_node_csb, "node_pieces")->setVisible(false);
        //显示亮图
        auto sp_normal_one = (Sprite*)PUI::of_find_by_name(l_node_csb, "sp_photo_normal_bg");
        PUI::of_sprite_set_texture(sp_normal_one, of_get_photo_path_by_id(li_photo_id));
    }

    //显示节日名,根据中英文决定谁不显示
    auto l_txt_photo_name_en = (Text*)PUI::of_find_by_name(l_node_csb, "txt_name_en");
    l_txt_photo_name_en->setString(g.candy_photo_collect->of_get_name(li_photo_id, g.is_country));
    l_txt_photo_name_en->setVisible(g.is_country == "zh" ? false : true);
    auto l_txt_photo_name_zh = (Text*)PUI::of_find_by_name(l_node_csb, "txt_name_zh");
    l_txt_photo_name_zh->setString(g.candy_photo_collect->of_get_name(li_photo_id, g.is_country));
    l_txt_photo_name_zh->setVisible(g.is_country == "zh" ? true : false);

    //隐藏节日说明
    PUI::of_find_by_name(l_node_csb, "txt_info_en")->setVisible(false);
    PUI::of_find_by_name(l_node_csb, "txt_info_zh")->setVisible(false);
}

//根据photo id找到对应的图片路径
string DialogPhoto::of_get_photo_path_by_id(int li_photo_id)
{
    return StringUtils::format("photoes/normal/%03d_normal.png", li_photo_id, li_photo_id);
}

string DialogPhoto::of_get_gray_photo_path_by_id(int li_photo_id)
{
    return StringUtils::format("photoes/gray/%03d_gray.png", li_photo_id, li_photo_id);
}

//拼好之后,亮图淡入完成后的回调
void DialogPhoto::of_callback_all_pieces_ok(int li_photo_id)
{
    auto l_layout_panel_for_touch = PUI::of_find_by_name(csb_root, "panel_for_touch");

    //显示节日说明
    auto photo_text_en = (Text*)PUI::of_find_by_name(l_layout_panel_for_touch, "txt_info_en");
    auto photo_text_zh = (Text*)PUI::of_find_by_name(l_layout_panel_for_touch, "txt_info_zh");
    photo_text_en->setString(g.candy_photo_collect->of_get_introduce(li_photo_id, g.is_country));
    photo_text_zh->setString(g.candy_photo_collect->of_get_introduce(li_photo_id, g.is_country));
    photo_text_en->setVisible(g.is_country == "zh" ? false : true);
    photo_text_zh->setVisible(g.is_country == "zh" ? true : false);

    g.is_message_for_dialog = "hammer,1;beforelineline,1;beforesame,1;coin_big,100;life_unlimited,1;beforebombbomb,1";
    g.cm->of_award_by_string(g.is_message_for_dialog);
    DialogAwardBox::make(this, 999, nullptr);
}

void DialogPhoto::of_work_after_piece_status_turn_to_ok(int li_photo_id, Vec2 piece_pos)
{
    //根据拼好了多少个碎片来决定飞过去的光效飞到哪个宝箱上
    int li_ok_piece_cnt = g.candy_photo_collect->of_get_photo_ok_piece_count(li_photo_id);

    //默认飞向第三个宝箱
    auto l_node_fly_to = PUI::of_find_by_name(csb_root, "btn_albumgiftbox_3");
    if (li_ok_piece_cnt <= 3) l_node_fly_to = PUI::of_find_by_name(csb_root, "btn_albumgiftbox_1");
    else if (li_ok_piece_cnt <= 9) l_node_fly_to = PUI::of_find_by_name(csb_root, "btn_albumgiftbox_2");

    //拼好一个碎片后,播放一个光效从碎片飞到宝箱上
    auto pos_fly_begin_t = m_touching_piece_father_node->convertToWorldSpace(piece_pos);
    auto pos_fly_begin = csb_root->convertToNodeSpace(pos_fly_begin_t);
    auto pos_fly_end_t = l_node_fly_to->convertToWorldSpace(Vec2(114, 117));
    auto pos_fly_end = csb_root->convertToNodeSpace(pos_fly_end_t);

    auto par = ParticleSystemQuad::create("particle/particle_juneng_adding.plist");
    par->setPosition(pos_fly_begin);
    csb_root->addChild(par);

    auto act1 = MoveTo::create(1, pos_fly_end);
    auto act2 = DelayTime::create(0.4f);
    auto act3 = CallFunc::create([this, li_photo_id] {
        //拼好一个碎片后,更新宝箱上面的数字
        refresh_award_box_label(li_photo_id);
        //拼好一个碎片后,检查是否发奖
        check_need_award_box(li_photo_id);
        //拼好一个碎片后,更新左侧列表框里的缩略图的显示
        refresh_list_view_photo(li_photo_id);
    });
    auto act4 = RemoveSelf::create();
    par->runAction(Sequence::create(act1, act2, act3, act4, nullptr));
}

#pragma endregion

过关之后的判断

void PanelFinish::of_photo_award_dialog() {
    if (g.ib_current_level_first_passed == false) return;

    //获取今天的日期
    int date_of_today = PFJava::of_get_today_number();
    date_of_today = date_of_today % 10000;
    //获取当前的关卡号
    int current_level = g.stages->of_get_passed_index_max();

    // 得到碎片图以及序号
    int li_photo_id = 0, li_piece_index = 0;
    bool lb_piece_found = g.candy_photo_collect->of_get_award_piece(g.game_model, date_of_today, current_level, 5, li_photo_id, li_piece_index);
    if (!lb_piece_found) return;
    g.candy_photo_collect->of_set_last_open_photo(li_photo_id);
    g.candy_photo_collect->of_set_piece_status(li_photo_id, li_piece_index, 1);


    //  注意这是一个480的csb
    string ls_csb = "csb_ui_photo/dialog_photo_get_piece.csb";

    auto d1 = PUI::of_create_dialog(layer, ls_csb, 1999, g.rrscale * 2.0f, 45);
    d1->setName("csbnode_dialog_photo_get_piece");
    g.of_set_ui_by_country(d1);


    auto  node_pos_photo_piece = PUI::of_find_by_name(d1, "node_pos_photo_piece");
    auto  sp_demo = PUI::of_find_by_name(node_pos_photo_piece, "sp_demo");
    auto pos0 = sp_demo->getPosition();
    sp_demo->setVisible(false);


    do {
        // 采用动态创建的方式进行创建碎片

        auto l_single_node = CSLoader::createNode("csb_ui_photo/node_photo_one.csb");
        CC_BREAK_IF(!l_single_node);

        auto l_node_pieces = PUI::of_find_by_name(l_single_node, "node_pieces");
        CC_BREAK_IF(!l_node_pieces);

        string ls_piece_name = StringUtils::format("sp_piece%d", li_piece_index);
        auto n_piece1 = static_cast<Sprite*>(l_node_pieces->getChildByName(ls_piece_name));
        CC_BREAK_IF(!n_piece1);

        Vec2 pos_cur_piece = n_piece1->getPosition();
        Size size_cur_piece = n_piece1->getContentSize();

        string ls_stencil_filename = n_piece1->getTexture()->getPath();
        CC_BREAK_IF(!FileUtils::getInstance()->isFileExist(ls_stencil_filename));

        auto l_sprite_stencil = Sprite::create(ls_stencil_filename);
        CC_BREAK_IF(!l_sprite_stencil);

        string ls_content_filename = StringUtils::format("photoes/normal/%03d_normal.png", li_photo_id, li_photo_id);
        CC_BREAK_IF(!FileUtils::getInstance()->isFileExist(ls_content_filename));

        auto l_sprite_content = Sprite::create(ls_content_filename);
        CC_BREAK_IF(!l_sprite_content);

        l_sprite_content->setAnchorPoint(Vec2::ANCHOR_BOTTOM_LEFT);
        l_sprite_content->setPosition(Vec2(10 - pos_cur_piece.x, 130 - pos_cur_piece.y));

        auto l_node_clip = ClippingNode::create(l_sprite_stencil);
        l_node_clip->setAlphaThreshold(0);
        l_node_clip->setInverted(false);
        l_node_clip->addChild(l_sprite_content);
        l_node_clip->setScale(0.45f);
        node_pos_photo_piece->addChild(l_node_clip);

        string ls_shield_filename = StringUtils::format("photoes/ui/001_%d_1.png", li_piece_index);
        CC_BREAK_IF(!FileUtils::getInstance()->isFileExist(ls_shield_filename));

        auto l_sprite_shield = Sprite::create(ls_shield_filename);
        CC_BREAK_IF(!l_sprite_shield);
        l_sprite_shield->setScale(0.45f);

        node_pos_photo_piece->addChild(l_sprite_shield);
    } while (0);

    // 
    auto button_close = (Button*)PUI::of_find_by_name(d1, "button_close");
    button_close->addClickEventListener(CC_CALLBACK_0(PanelFinish::of_photo_award_dialog_close, this));

    auto button_open = (Button*)PUI::of_find_by_name(d1, "button_open");
    button_open->addClickEventListener(CC_CALLBACK_0(PanelFinish::of_photo_award_dialog_open, this));

    g.of_track_function("n_photo_collect_piece");

    g.play_sound("music/sound_gift_get.mp3");

}
void PanelFinish::of_photo_award_dialog_open() {
    PUI::of_find_by_name_set_visible(layer, "csbnode_dialog_photo_get_piece", false);

    DialogPhoto::make(layer, 1999, nullptr);

}
void PanelFinish::of_photo_award_dialog_close() {
    auto d1 = PUI::of_find_by_name(layer, "csbnode_dialog_photo_get_piece");
    d1->setVisible(false);

}

配置文件

保存为photo_collect_config.json,放在config目录下。

{
	"说明1": "type_date是日期类型的配置,日期类型的配置里不能有level_begin字段",
	"说明2": "type_level是关卡类型的配置,关卡类型的配置里不能有date_begin和date_end字段",
	"说明3": "id这个字段必须是大于0的整数,并且我们约定1到500表示日期类配置,501到999表示关卡类配置。当然你也可以不遵守这个约定,不影响程序正常运行。这个id的作用是用来找到对应的图片资源",
	"说明4": "日期类配置,date_end必须大于或等于date_begin,等于就表示这个节日只有一天",
	"说明5": "关卡类配置,level_begin必须各不相同",
	"说明6": "日期类配置,同一个日期区间可能会对应多张图,比方说父亲节有3张图,现在到父亲节的日期了,并且3张图都没解锁,那么谁先解锁谁后解锁,可以再加一个关卡号来筛选,也就是level_limit这个字段,这个字段可有可无",
	"type_date": [{
		"id": 1,
		"name_en": "Christmas Day",
		"name_zh": "圣诞节",
		"info_en": "Luna is going to have a wonderful Christmas with him(or her), Who would be the lucky one?",
		"info_zh": "露娜将会跟他(她)在这一起度过一个愉快的圣诞节,那位幸运儿回是谁呢?",
		"date_begin": 1201,
		"date_end": 1231
	}, {
		"id": 2,
		"name_en": "Valentine's Day",
		"name_zh": "情人节",
		"info_en": "Luna received a beautiful bouquet of flowers, Who gave her?",
		"info_zh": "露娜收到一束漂亮的花,是谁送给她的?",
		"date_begin": 201,
		"date_end": 218
	}, {
		"id": 3,
		"name_en": "Women's Day",
		"name_zh": "妇女节",
		"info_en": "Make your own life!",
		"info_zh": "创造属于你自己的生活吧!",
		"date_begin": 301,
		"date_end": 310
	}, {
		"id": 4,
		"name_en": "St.Patrick's Day",
		"name_zh": "圣帕特里克节",
		"info_en": "Let the shamrocks start your luck.",
		"info_zh": "让三叶草给你带来好运!",
		"date_begin": 311,
		"date_end": 318
	}, {
		"id": 5,
		"name_en": "April Fool's Day",
		"name_zh": "愚人节",
		"info_en": "A bottle of wine will be delivered in 5 mins.",
		"info_zh": "5分钟后会有人给你送一瓶葡萄酒。",
		"date_begin": 327,
		"date_end": 402
	}, {
		"id": 6,
		"name_en": "Easter",
		"name_zh": "复活节",
		"info_en": "Go and find the eggs hidden in the game.",
		"info_zh": "去寻找游戏里的彩蛋吧!",
		"date_begin": 406,
		"date_end": 428
	}, {
		"id": 7,
		"name_en": "Mother's Day",
		"name_zh": "母亲节",
		"info_en": "This is a photo of Luna childhood when she was with her mom on Mother's Day",
		"info_zh": "这是露娜小时候陪妈妈一起过母亲节的照片!",
		"date_begin": 501,
		"date_end": 518
	}, {
		"id": 8,
		"name_en": "Halloween",
		"name_zh": "万圣节",
		"info_en": "It was the first time that she attend Halloween party when she was 6 years old.",
		"info_zh": "这是露娜6岁时参加万圣节派对的照片,她获得了很多糖果!",
		"date_begin": 1020,
		"date_end": 1120
	}, {
		"id": 9,
		"name_en": "Father's Day",
		"name_zh": "父亲节",
		"info_en": "Today is Father's Day.I send dad a beautiful bouquet of flowers.",
		"info_zh": "父亲节到了,送爸爸一束美丽的鲜花!",
		"date_begin": 610,
		"date_end": 630,
		"level_limit": 60
	}, {
		"id": 10,
		"name_en": "Father's Day",
		"name_zh": "父亲节",
		"info_en": "On Father's Day,I prepared a lot of gifts for my father.",
		"info_zh": "在父亲节这天,我给爸爸准备了很多的礼物。",
		"date_begin": 610,
		"date_end": 630,
		"level_limit": 50
	}, {
		"id": 11,
		"name_en": "Independence Day",
		"name_zh": "独立日",
		"info_en": "Today is Independence Day,Luna dressed up as a holiday costume,ready to go to a community party.",
		"info_zh": "今天是独立日,露娜装扮了节日的装束,准备去参加社区的派对",
		"date_begin": 701,
		"date_end": 720
	}],
	"type_level": [{
		"id": 501,
		"name_en": "Go to the beach",
		"name_zh": "去沙滩",
		"info_en": "Luna had a good time on the beach!",
		"info_zh": "露娜在沙滩玩得很开心!",
		"level_begin": 50
	}, {
		"id": 502,
		"name_en": "Making Chocolate",
		"name_zh": "爱心巧克力",
		"info_en": "Luna's making chocolate hearts.Who will receive them?",
		"info_zh": "露娜在做爱心巧克力,谁会收到它呢?",
		"level_begin": 30
	}, {
		"id": 503,
		"name_en": "Hold a Dance",
		"name_zh": "舞会",
		"info_en": "Who will invite Luna for first dance on the ball?",
		"info_zh": "舞会上,谁有幸邀请露娜跳第一支舞?",
		"level_begin": 60
	}, {
		"id": 504,
		"name_en": "Play the Guitar",
		"name_zh": "弹吉他",
		"info_en": "Guess which tune will James play for Luna today?",
		"info_zh": "猜猜詹姆斯今天想为露娜弹什么曲子?",
		"level_begin": 90
	}, {
		"id": 505,
		"name_en": "Make Up",
		"name_zh": "化妆",
		"info_en": "Luna is dressing herself up.Who will she hang out with?",
		"info_zh": "露娜在精心打扮,她待会儿要去见谁?",
		"level_begin": 120
	}, {
		"id": 506,
		"name_en": "Watch Stars",
		"name_zh": "看星星",
		"info_en": "Puff feels like seeing a starry night with Luna.",
		"info_zh": "普夫好像和它的主人一起看星空呢!",
		"level_begin": 160
	}, {
		"id": 507,
		"name_en": "Candlelight Dinner",
		"name_zh": "烛光晚餐",
		"info_en": "Who will Luna have candlelight dinner with?",
		"info_zh": "邀请露娜共进晚餐的那位是谁呢?",
		"level_begin": 200
	}]
}

一句话说明

base64是一种用64个字符来表示任意二进制数据的方法。
把3字节的二进制数据编码成4字节的文本数据。

详细解释

  1. 原则

    二进制文件中包含很多无法显示和打印的字符。所以我们需要把不可显示的字符转化成可显示的字符。

  2. 是哪64个字符

    26个英文字母两组,分大小写,共52个。0到9共10个。一个 + ,一个 / 。凑齐64个。

  3. 怎么转

    拿到二进制数据后,3个字节一组,共24位,分成4组,6位一组。即 3*8=4*6

    现在得到了4组,每一组前边加两个0,就成了4组8位,即4个字节。

  4. 为什么偏偏是64

    6位二进制有2的6次方种可能,就是64种可能。

  5. 二进制数据的字节数量不是3的倍数怎么办?

    假如每3个字节切一刀,切到最后剩一个或两个字节怎么办?规则是在编码的末尾加一个或两个等号。

  6. base64编码的变种

    为了能写在URL中, +/ 变成了 -_

0%