2015-12-28 13:55:42 u014486880 阅读数 9419

做游戏是个很好玩的事情,由于兴趣我最近也在学习如何用android做游戏。顺便它选择了植物大战僵尸来当我的嵌入式作业 。熬了两天两夜终于把它完成了。这个过程从学习调研到写代码的各种心塞到完工,花了很多时间,也花了很多精力,但觉得是值得的。本文并不是讲解植物大战僵尸是怎么做的,而是讲解CoCos2d_android常用的基础知识,当然只要知道这些知识也就能够能完成这个游戏,最后会把代码贴出。先看下效果图:


一.Cocos2d_android介绍

Cocos2d是一个大家庭,包括Cocos2d-iphone,Cocos2d-x,Cocos2d-javascript等。而在国内,Cocos2d-x则相对领先。在中国的2D手机游戏开发中,Cocos2d-x引擎的份额超过70%。不同家庭成员之间只是语言不同,而实现的接口名称都相同。所以只要学习一个,其它的就都比较好理解了。本文讲的是Coscos2d_android,因为它是用java实现的,所以对我来说学起来比较快。

二.Cocos2d_android架构


如上图,Cocos2d这游戏引擎主要由图形引擎(Graphic),声音引擎(Audio),物理引擎(Box2d),脚本库以及相关语言等组成。我们重点关注开发过程中需要注意的。先看一张Cocos2d_android的代码结构图:


对于Cocos2d_android只要关注四个部分, CCDirector(导演),CCScene(场景),CCLayout(幕布)以及CCSprite(精灵)。我们可以把它当成在拍电影,顾名思义可以看出它们的作用:

CCDirector:电影中的导演,肯定是负责整部电影拍摄的,它有三个功能,管理CCScene,开线程执行SurfaceView中的绘制行为,设置游戏属性。

CCScene:电影中的场景,当然包括人和背景。可以理解它是根View,layer都必须建立在它之上,有点类似activity与fragment的关系。

CCLayer:场景中的部分图层,离用户最近的一层。游戏过程中始终只有一个layer能获得焦点。每个动作都必须建立在layer上。

CCSprite:精灵,这个可以理解为activity中的一个控件。就是最小的一部分了。平时控制最多的也就是它,所以要重点关注。

三.四大组成部分用法

其它先不说,首先你要用Cocos2d_android,你就应该先把包导进来(可以在我的工程下的libs中找到)。接下来讲解下上面讲的四部分如何在代码里面用。首先是CCDirector,直接看MainActivity中CCDirector的设置代码:

protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		CCGLSurfaceView surfaceView = new CCGLSurfaceView(this);
		setContentView(surfaceView);
		director = CCDirector.sharedDirector();
		director.attachInView(surfaceView);//开线程
		director.setScreenSize(480, 320);
		director.setDeviceOrientation(CCDirector.kCCDeviceOrientationLandscapeLeft);//横屏
		director.setDisplayFPS(true);//不显示帧率
		CCScene scene = CCScene.node();
		scene.addChild(new WelComeLayer());
		//导演管理场景
		director.runWithScene(scene);
	}

	@Override
	protected void onResume() {
		director.onResume();
		super.onResume();
	}

	@Override
	protected void onPause() {
		director.onPause();
		super.onPause();
	}

	@Override
	protected void onDestroy() {
		director.end();
		super.onDestroy();
	}
CCGLSurfaceView继承着SurfaceView,由此可见游戏引擎是用SurfaceView做的。CCDirector是一个单例,调用sharedDirector来获取它,保证全局只有一个CCDirector。初始化完后就要开始工作了,首先attachInView与surfaceView连接,感兴趣的可以进去看下attachInView源码,它跟我们平时使用surfaceView很相似,也是开了个线程,然后在幕布上画图像。这里主要是讲使用,就不翻源码了。setDeviceOrientation设置屏幕横屏。setDisplayFPS设置为true时就会在游戏左下角显示帧率,这个只是在开发时使用。最后很关键的就是初始化一个CCScene,并调用runWithScene把场景加进去。这样CCDirector的初始工作就结束了

接下来看CCScene,它是根View,初始时经过runWithScene加入到CCDirector中,不同游戏界面可以理解为不同layer,切换layer可以理解为切换activity一样,而切换的代码如下,基本可以看成是固定格式的。

    CCScene scene = CCScene.node();
scene.addChild(new FightLayer());
CCFadeTransition transition = CCFadeTransition.transition(0.5F, scene);
	//替换场景
	CCDirector.sharedDirector().replaceScene(transition);
transition是一个切换动画,CCFadeTransitiion只是切换动画的一种,具体可以察看api。

好,我们要玩游戏就要有界面,界面画在哪上面?xml,还是activity。那么接下来这位成员就派上它的用场了。layer需要我们自己来实现,它要继承自CCLayer,如以下是自定义的layer:

public class WelComeLayer extends CCLayer {
	public WelComeLayer(){
		init();
	}}

只要把你自己的实现写在init()方法中就可以了。什么时候被调用就要看什么时候它被addChild到scene中。layer中就可以处理很多事情,包括地图的加载,音乐的播放,精灵的移动等。这些后面会细讲。最后就是精灵啦。

CCSprite是用的最多的,一个僵尸可以是一个精灵,一张图片一段文字都可以是一个精灵。精灵的加载如下:

choseContainer = CCSprite.sprite("fight_chose.png");
		choseContainer.setAnchorPoint(0, 1);
		choseContainer.setPosition(0, cgSize.height);
		this.addChild(choseContainer,0,1);
这里是把assets下的一张图片当作精灵,setAnchorPoint是设置锚点,(0,1)就是图片的左上角,相当于图片钉在左上角上,移动旋转时它都为中心。setPosition不用说,就是设置精灵的位置啦。然后调用layer的addChild就把它加载进来了。这里它有三个参数,第一个是精灵,第二个是它的层数,0是最底层的,如果想把它置于上层,就给它设置一个值,值越大层数越高,就越不会被覆盖。最后一个参数是tag,设置了它就可以在其它地方通过tag来获取这个精灵,类似于findViewById(),获取代码如下:

CCSprite c = layer.getChildByTag(1);
注意上面的layer必须是你定义精灵所在的layer中。那么如何在layer中监听点击事件呢,看以下代码:

//设置物体的触发事件
@Override
public boolean ccTouchesBegan(MotionEvent event) {
     // TODO Auto-generated method stub
     CCSprite sprite =  (CCSprite) this.getChildByTag( TAG_X);
     //转成opengl下的坐标点
     CGPoint cgPoint = this.convertTouchToNodeSpace(event);
     boolean flag = CGRect.containsPoint(sprite.getBoundingBox(), cgPoint);
     if(flag){
            //设置透明度
           sprite.setOpacity(100);
            //设置是否可见
           sprite.setVisible( false);
            //删除自己
           sprite.removeSelf();
     }
     return super.ccTouchesBegan(event);
}
类似于OnEventTouch(),这里有一个知识点,就是android平时的习惯是把屏幕左上角当坐标原点,向下的y正,向右是x正。但是在Cocos2d中不一样,你必须把它转成openGl的习惯,也就是左下角是坐标,向上是y正,向右是x正。转化也有api,如上面的convertTouchToNodeSpace()就是把android的point转成OpenGL下的Point。

四,加载资源文件

讲到这里,我假设你之前的都己经明白了,也能在不同界面上显示不同的sprite了,那接下来我们就要对游戏场景进行优化,首先光一些精灵肯定不行,肯定要有背景图,而且游戏嘛,肯定也要有音乐。那首先来讲讲地图的加载吧。

其实地图加载我认为一点都不简单,别以为地图只是一张图片,如果是张图片,那游戏开发过程中要定位位置怎办。其实在游戏中有一个格式的文件很见,.tmx文件,它包括地图上某些你标记的点的信息。这样的图片用文本编辑器打开如下:

<?xml version="1.0" encoding="UTF-8"?>
<map version="1.0" orientation="orthogonal" width="14" height="6" tilewidth="46" tileheight="54">
 <tileset firstgid="1" name="bk1" tilewidth="46" tileheight="54">
  <image source="bk1.jpg" width="678" height="331"/>
 </tileset>
 <layer name="block" width="14" height="6">
  <data encoding="base64" compression="zlib">
   eJwNw4lSQQEAAMAXEZWEpKjooEShkhJdEkmS+v9vaXdml4IgCBl22YhRV4wZd9U11024YdJNU6bNuGXWbXPuuGvegnvue2DRkoceeeyJZSueembVc2vWvfDShk1bXnntjW1v7XjnvV0f7Nn30SefffHVgW8OfXfk2A8nfjr1y5nfzv1x4a9//gNAug3z
  </data>
 </layer>
 <objectgroup name="road" width="14" height="6" visible="0">
  <object x="473" y="96"/>
  <object x="474" y="306"/>
  <object x="23" y="303"/>
 </objectgroup>
</map>
它会被解析成一个xml格式的文件,里面记录了地图长宽,及长宽分为几块,以及一些标记点的集合。如上面name为“road”的集合。看到这你是不是吓到了,难道每个地图都要自己手写?这肯定是不可能的。这里介绍一款小软件,tiled(下载链接)。它可以方便的进行地图标记,显示如下图:

如果你想改变背景图的样子,可以用上边栏的填充工具等进行绘制,如果想定点就新建一个对象层,如同图中的road。再用工具栏上的创建对象按钮在地图上定点,记住定点的顺序是有讲究的,先确定的点会存在前面,这个待会解析时会说,可以创建多个对象层,起的名字就是文件中objectgroup的name属性对应的名字。图片制作完成后保存就自动更改为.tmx格式。把它放到assets文件下,就可以加载它了,来看下加载的代码:

map = CCTMXTiledMap.tiledMap("map_day.tmx");
		map.setAnchorPoint(0.5f,0.5f);
		CGSize contentSize = map.getContentSize();
		//左移右移一半
		map.setPosition(contentSize.width/2,contentSize.height/2);
		this.addChild(map);
CCTMXTiledMap.tiledMap就是把图片加载进来,getContentSize就是获取地图的大小,之所以把地图的位置置为大小的一半,是因为地图可能大于屏幕,这样设置希望它一开始就能显示左下部分。那如何获得我们在地图上设置的点呢,如下:
CCTMXObjectGroup zombiesGroup = map.objectGroupNamed("road");
		ArrayList<HashMap<String, String>> zombies = zombiesGroup.objects;
		// 分别以x和y为键,获取坐标值信息---->封装到点集合中
		List<CGPoint> points = new ArrayList<CGPoint>();
		for (HashMap<String, String> item : zombies) {
			float x = Float.parseFloat(item.get("x"));
			float y = Float.parseFloat(item.get("y"));
			points.add(CGPoint.ccp(x, y));
		}
这个可以说是固定格式,你可以把它写到工具类中,最后points这个list中点信息的顺序就是你在地图上添加点的顺序,所以添点忌随意。有了点你就能干很多事,如设置移动路线。地图加载讲完后,就是音乐加载了。

这样相对比较简单,音乐的话要获得一个音乐引擎来加载音乐。音乐引擎其实就是封装了下mediaPlayer而已。看下代码:

SoundEngine engine = SoundEngine.sharedEngine();
		engine.preloadSound(getContext(), R.raw.start);
engine.playSound(getContext(), com.lxj.zhiwuvsani.R.raw.start, true);

如上,raw文件下放一些音乐文件,preloadSound是预先加载,可以把它放在游戏加载过程中,而playSound就是你想在哪播就设置在哪。true表示循环播放。

最后,说下字体的加载吧,Cocos2d_android字体用的是CCLabel,它继承自CCSprite,设置如下:

CCLabel cLabel = CCLabel.makeLabel("hello world", "Roboto_Thin.ttf", 20);//创建字体,中间参数为ttf文件,20为字体大小
			cLabel.setPosition(cgSize.width/2,cgSize.height/2);
			this.addChild(cLabel,1);

它加载的字是hello world,加载的字体格式是Roboto_Thin.ttf,这个文件要存在assets文件夹下,如有不想设置那就直接置为“”。

讲了这么多,现在己经可以自己布置场景,加载精灵任意摆放,并配上音乐字体了。一个游戏界面就差不多了,但是,很重要的一点,东西都不会动!!!!,不会动怎么玩。下一篇我将讲讲如何让精灵动起来,设置各种action及判断位置。看完下篇,你就能完整的做游戏了。当然看这些可能还不够,这些只是最常用的,对于一些不常用的不懂的时还要去查下api,好啦,说了这么多,好累,歇会。

植物大战僵尸源码下载









2018-05-28 10:40:10 qq_33483897 阅读数 0

转载博客:http://blog.sina.com.cn/s/blog_61ece0990102vay6.html

现在Cocos2dx开发的网游基本上都支持热更新功能,但大多都用LUA语言开发,由于我们项目启动时间较早,开发语言为C++,今天就把自己的项目是怎么一步一步实现热更新的, 分享给大家:

 

平台:android

cocos2dx版本:3.0正式版

 

最初的思路是这样的: 

1,实现游戏资源的热更新。

2,实现代码库的热更新。

 

经最后实验把代码库的热更新也列为游戏资源更新, 这样就更方便了,两者基本上是一样的。

 

一、首先了解Android有一个System.load()方法

调用 System.load 从手机的扩展卡中加载游戏的so文件,具体这个so文件是怎么放到你的扩展卡中, 了解一下如何使用android的文件操作api就懂了。

 

二、cocos2dx中设置资源搜索路径

 

//android端获取资源目录

 std::string jinResourcePath = GameJniHelper::getResourcePath();

 

//重新设置 FileUtils 的 SearchPaths,注意:第一个path为android端发过来的。

 vector searchPaths;
 searchPaths.clear();

 searchPaths.push_back(jinResourcePath);
 vector paths = FileUtils::getInstance()->getSearchPaths();
 for (int i=0; i
 {
  searchPaths.push_back(paths[i]);
 }


 FileUtils::getInstance()->setSearchPaths(searchPaths);

 

以上主要实现在cocos2dx加载资源的时候优先选择扩展卡中的资源, 因为这里的资源才是最新的。

这里注意的是:加载tmx文件的时候, tmx文件必须与纹理的目录问题, 这个是cocos2dx代码的问题。

 

 

三、使用网络下载更新资源,做一系列的资源比对操作, 然后保存版本。

这块代码比较多, 但是业务相对来说比较简单的。


2019-12-26 00:52:39 NDSoumig 阅读数 42

CrazyDao层

package com.liang.a.util;

import android.database.Cursor;
import android.util.Log;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class CrazyDao {
    public static ArrayList<Crazy> inputSingleJson(String json){
        String[] jsonAll = json.split("\\},");
        ArrayList<Crazy> list_all = new ArrayList();
        for (int i=0 ; i < jsonAll.length ; i++){
            Crazy crazy = new Crazy();
            String[] splitJson = jsonAll[i].split(",");
            crazy.setAnswer(splitJson[0].split(":")[1].split("\"")[1]);
            crazy.setId(Integer.parseInt(splitJson[1].split(":")[1]));
            crazy.setQuestion_img(splitJson[2].split(":")[1].split("\"")[1]);
            crazy.setQuestion_type(splitJson[3].split(":")[1].split("\"")[1]);
            crazy.setSelect_txt(splitJson[4].split(":")[1].split("\"")[1]);
            list_all.add(crazy);
        }
        return list_all;
    }

    private static String readInputStreamToString(InputStream is) {
        byte[] result;
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            is.close();
            baos.close();
            result = baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            return "获取数据失败";
        }
        return new String(result);
    }

    public static int getDataSize() {
        int size=0;
        try {
            URL url = null;
            url = new URL("http://10.0.2.2:8080/AndroidTest/GetAnserSize");
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setReadTimeout(5000);
            conn.setRequestMethod("GET");
            String resp_content = "";
            int code = conn.getResponseCode();
            if (conn.getResponseCode() == 200) {
                InputStream is = conn.getInputStream();//从这里获取JSON数据,然后放到readInputStreamToString里面解析
                size = Integer.valueOf(readInputStreamToString(is));//54
            } else {
                Log.e("fail", "fail");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return size;
    }

    public static List<Map<String,Object>> getStageData(int nowStage){
        List<Map<String,Object>> values = new ArrayList<>();
        try {
            ArrayList<Crazy> list = new ArrayList();
            URL url = null;
            url = new URL("http://10.0.2.2:8080/AndroidTest/CheckAllServlet");
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setReadTimeout(5000);
            conn.setRequestMethod("GET");
            String resp_content = "";
            int code = conn.getResponseCode();
            if (conn.getResponseCode() == 200) {
                InputStream is = conn.getInputStream();
                resp_content=readInputStreamToString(is);
                list = inputSingleJson(resp_content);
                for(int i=0;i<list.size();i++){
                    Map<String ,Object> map = new HashMap<>();
                    map.put("stage",list.get(i).getId());
                    if (nowStage >= list.get(i).getId()){//如果当前关不比取得的关数小
                        map.put("overFlag",true);//设置成true,证明玩过了的关
                    }else{
                        map.put("overFlag",false);//证明没玩儿过的关
                }
                values.add(map);//添加到列表中
                }
            } else {
                Log.e("fail", "fail");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return  values;
    }
    public static Map<String,Object> getDataById(int id){
        Map<String,Object> map = new HashMap<>();
        try {
            ArrayList<Crazy> list = new ArrayList();
            URL url = null;
            url = new URL("http://10.0.2.2:8080/AndroidTest/CheckMesById?id="+id);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setReadTimeout(5000);
            conn.setRequestMethod("GET");
            String resp_content = "";
            int code = conn.getResponseCode();
            if (conn.getResponseCode() == 200) {
                InputStream is = conn.getInputStream();
                resp_content=readInputStreamToString(is);
//                Log.e("content",resp_content);
                JSONObject json = new JSONObject(resp_content);
                map.put("answer",json.getString("answer"));
                map.put("questionType",json.getString("question_type"));
                map.put("questionImg",json.getString("question_img"));
                map.put("selectTxt",json.getString("select_txt"));
            } else {
                Log.e("fail", "fail");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return map;
    }
}
2015-11-10 19:59:06 sinat_16095273 阅读数 344

我们在主场景的触摸事件中,已经判定了玩家选择的植物类型,以及放置的位置,并把它们设置给了战斗层FightLayer,然后调用FightLayer的添加植物方法addPlant添加了植物,植物类的创建我会在后面讲到,现在我们就先当这些类是存在的就好了;我们就来看看这个函数的实现:

因为要用到植物对象,所以我们先把植物类导进来:

local Nut = require("app.plant.Nut")
local Pease = require("app.plant.Pease")
local SunFlower = require("app.plant.SunFlower")

下面是添加植物方法的实现

function FightLayer:addPlant(event)
-- checkint 检查是否为数字并且转换为整形
-- 根据玩家所点的位置,计算出行数和列数
local col=checkint((event.x+LAWN_WIDTH/2-58)/LAWN_WIDTH)
    local row=checkint((event.y+LAWN_HEIGHT/2)/LAWN_HEIGHT)
    -- 如果越界,返回
    if col<1 or col >9 or row<1 or row>5 then 
   return 
end
-- 如果对应的位置可以放植物并且植物类型不为NOT_PLANT,即说明玩家有选择某一种类型的植物
if self.lawnMatrix[row][col] and self.choosePlantType~=NOT_PLANT then
-- 在该行该列中添加对应类型的植物
   self:addPlantObj(row,col)
   -- 此时该位置不能放植物了
   self.lawnMatrix[row][col]=false
   -- 植物添加之后,表示有没有选择植物
   self.choosePlantType=NOT_PLANT
   -- 被选择的卡片
   local plantCard=self:getParent().myPlants[self:getParent().clickCardIndex]
   -- 玩家能力值减少
   self:getParent().dataLayer:loseEnergy(plantCard.energy)
   self:getParent().clickCardIndex=0
   -- 卡片冷却
   plantCard.isCooling=true
   -- 5秒后冷却结束
   plantCard.plantCard:runAction(cc.Sequence:create(cc.FadeTo:create(5.0, 255),cc.CallFunc:create(function()
    plantCard.isCooling=false
   end)))


     end
end
更具植物类型创建并添加植物
function FightLayer:addPlantObj(row,col)
local plant=nil
-- 如果是坚果类型
if self.choosePlantType==NUT then
-- 创建坚果
plant = Nut.new()
:addTo(self)
-- 添加到非攻击类型植物的数组中
table.insert(self.defensePlants,plant)
elseif self.choosePlantType==SUNFLOWER then
-- print("添加向日葵")
plant = SunFlower.new()
:addTo(self)
table.insert(self.defensePlants,plant)
elseif self.choosePlantType==PEASE then
-- print("添加豌豆射手")
plant = Pease.new()
:addTo(self)
table.insert(self.attackPlants,plant)
end
-- 植物所在列和行
plant.row=row
plant.col=col
-- 将植物添加到植物table中
table.insert(self.allPlants,plant)
-- 更具行列数设置植物的位置
plant:setPosition(cc.p(col*LAWN_WIDTH+40,row*LAWN_HEIGHT-15))
end

运行效果如下:


2017-01-12 13:21:47 JeterPong 阅读数 360

前言
对于Cocos 这一块,完全是个菜鸟,虽然很多看不懂,但做项目需要用到,没办法只能自己慢慢摸索前进了. (•̀ᴗ•́)و ̑̑

环境:Cocos2d-js 、Android

Cocos2d 里面调用Java 方法操作,在制作Android版本时,需要使用C++调用java的函数,这个通过jni实现.

下面是 总结的几种 示例,记录一下,以后忘了还可以温故一下 ╰( ̄▽ ̄)╭。

C++ 调用 java :

#inclde "jnixxx.h" // 头文件
#ifder WIN32
#else
#define CLASS_NAME "com/xxx/xxxx/xxActivity"  // 需要调用的完整类名

extend "C"{

// 调用 java 返回值为 String 方法
std::string jniGetTest(){
  std::string strTest; 
  JniMedhodInfo info;
  bool ret = JniHelper:: getStaticMethodInfo(info, CLASS_NAME, "getTest", "()Ljava.lang/String;"); // 调用java 的静态方法 getTest();
  jstring jsStr;
  if(ret){
    jsStr = static_cast<jstring>(info.env->CallStaticObjectMehod(info.classID, info.methodID));
    const char* s = info.env->GetStringUTFChars(jsStr, NULL);
    if(s){
      strTest;
      info.env->ReleaseStringUTFChars(jsStr, s);
    }
    info.env->DeleteLocalRef(info.classID);
    return strTest;
  }

 }


// 调用 java 返回值为 boolean 方法
bool jniIsNetWork(){
  JniMethodInfo info;
  bool ret = JniHelper::getStaticMehodInfo(info, CLASS_NAME, "isNetWork", "()Z"); // 调用java 的静态 boolean 方法 isNetWork();
  bool isConnect;
  if(ret){
    isConnect = static_cast<bool>(info.env->CallStaticObjectMethod(info.classID, info.methodID));
    info.env->DeleteLocalRef(info.classID);
  }
  return isConnect;
 }

// 调用 java 返回值为 void 方法
void jniGetLogin(){
  JniMethodInfo info;
  bool ret = JniHelper::getStaticMehodInfo(info,CLASS_NAME, "onLogin", "()V"); // // 调用java 的静 void 态方法 isNetWork();
  if(ret){
    info.env->CallStaticObjectMehtod(info.classID, info.methodID);
    info.env->DeleteLocalRef(info.classID);
  }
 }
}
#endif

java代码:

// 返回 String 类型 方法
public static String getTest(){
  String str = "getTest";
  return str;
}

// 返回 boolean 类型 方法
public static boolean isNetWork(){
  boolean isConnect = false;
  return isConnect;
}

// void 类型 方法
public static void onLogin(){
  startActivity(this, new Intent(this, LoginActivity,class));
}

cocos2dx之文字输入

阅读数 6926