Оглавление
Cocos2d: Начало
Я начал изучать Cocos2d из-за его популярности, кросплатформенности и отличных возможностей.
На данный момент меня сильнее всего интересует Cocos2d-X с выбором языка программирования javascript, поскольку код Cocos2d на javascript можно выполнять в браузере и преобразовать для многих платформ в C++ (используя SpiderMonkey, Firefox JS виртуальную машину (VM)).
Плюсы:
Разрабатывается с 2010 года
Популярен, много топовых мобильных игр на кокосе
cross-platform
Поддержка C++ и javascript. Имеется встроенная поддержка популярных скриптовых языков.
Хорошее API
Open Source,
MIT License, бесплатен
Минусы:
Мало уроков на javascript для версии 3
Не все возможности cocos2d-x есть при программировании на javascript. Нет в js поддержки 3d (добавляется).
Возможно придется учить китайский, разрабатывается и документируется китайской Chukong Technologies
Не поддерживается сейчас в javascript (можно реализовать самому используя cc.sys.isNative):
jsb.Animate3D
jsb.Animation3D
jsb.Sprite3D
jsb.Skeleton3D
jsb.Mesh
jsb.AttachNode
jsb.BillBoard
jsb.sprite3DCache
jsb.ParticleSystem3D
jsb.PUParticleSystem3D
jsb.BaseLight
jsb.DirectionLight
jsb.PointLight
jsb.SpotLight
jsb.AmbientLight
Найденные в интернете уроки я решил собирать в один большой список уроков: https://gist.github.com/derofim/568e52ae75aff70a7213
На этой странице я буду публиковать собственные уроки, преимущественно используя javascript как язык программирования (javascript проекты можно запускать в браузере).
Свежая документация (многого нет еще на сйте): https://github.com/slackmoehrle/cocos-docs/tree/92bba2dcc94ad718c1bfbdde37d47b19da879e6a
Официальные примеры: https://github.com/cocos2d/cocos2d-x-samples
API: http://cocos2d-x.org/docs/api-ref/
Issues: https://github.com/cocos2d/cocos2d-x/issues
Официальный IRC кокоса: Freenode подключаться к #cocos2d
Код стайл:
C++: https://github.com/cocos2d/cocos2d-x/blob/v3/docs/CODING_STYLE.md
Python: https://www.python.org/dev/peps/pep-0008/
Cocos2d: Установка
Я воспользовался установщиком WINDOWS V3.10 INSTALLER (BETA) с официального сайта.
Команда cocos -v выводит 2.1, установлена только 3.10 версия движка.
Создать первый проект можно с помощью встроенных интрументов
Но можно воспользоваться командной строкой http://www.cocos2d-x.org/docs/editors_and_tools/cocosCLTool/index.html.
Убедитесь, что установлены необходимые программы и компоненты:
- Python 2.7.5
- NDK r10c+ is required to build Android games
- Windows Phone/Store 8.1 VS 2013 Update 4+ or VS 2015
- Windows Phone/Store 10.0 VS 2015
- JRE or JDK 1.6+ is required for web publishing
-
- iOS 6.0+ for iPhone / iPad games
- Android 2.3+ for Android games
- Windows 8.1 or Windows 10.0 for Windows Phone/Store 8.1 games
- Windows 10.0 for Windows Phone/Store 10.0 games
- OS X v10.6+ for Mac games
- Windows 7+ for Win games
- Modern browsers and IE 9+ for web games
-
JDK/SDK 1.6+ http://www.oracle.com/technetwork/java/javase/downloads/index.html
-
Android Studio Bundle http://developer.android.com/sdk/index.html
-
NDK r10c https://developer.android.com/tools/sdk/ndk/index.html
-
Apache Ant http://ant.apache.org/bindownload.cgi
-
Python 2.7.5 https://www.python.org/downloads/
Не забудьте установить переменные среды! Например jdk:
Подробные иструкции есть в документации http://www.cocos2d-x.org/docs/installation/F/index.html
В папке установки cocos2d не забудьте запустить setup.py для настройки переменных.
Пример создания javascript проекта в папке C:\MyGame:
cocos new MyGame -p com.MyCompany.MyGame -l js -d C:\MyGame
Компиляция: cocos compile . -p ios -m release
Поддерживаемые платформы: ios, android, mac, linux, win32, wp8_1 и web
Запуск: cocos cocos run . -p ios -m release
Подробнее о консоли кокоса: http://www.cocos2d-x.org/docs/editors_and_tools/cocosCLTool/index.html
Решение многих проблем с консолью: https://github.com/chukong/cocos-docs/blob/cf06ac86f6fb9630c8a65c1dcd4813eb5e95aa6f/manual/studio/v4/chapter4/FixPackageError/en.md
Cocos Studio Manual: https://github.com/chukong/cocos-docs/blob/cf06ac86f6fb9630c8a65c1dcd4813eb5e95aa6f/manual/studio/v4/category/en.md
Возможно вы захотите запустить игру на сервере. Можно установить локальный сервер, например openserver, denwer или wampserver. Можно воспользоваться хостингами, например heroku или Amazon E2. При желании можно настроить облачный редактор, например c9.io.
Еще лучше использовать node, например, https://www.codetutorial.io/livereload-with-grunt/ и https://webpack.github.io/docs/usage-with-grunt.html
В браузере можно осуществлять отладку приложения, есть специальные плагины, например, http://h5.cocos.com/static/cocos-devtools/index-en.html
Настройка Webstorm: https://github.com/chukong/cocos-docs/blob/v3-unified-documentation/tutorial/framework/html5/parkour-game-with-javascript-v3.0/chapter1/en.md
Настройка Sublime: http://www.sitepoint.com/essential-sublime-text-javascript-plugins/
Можно использовать готовые шаблоны приложений https://github.com/goodzsq/cocos_template
Публикация приложения и полезности:
[Tutorial] Google Play Game Services with Cocos2d-js and Android http://blog.rhesoft.com/2015/04/20/tutorial-google-play-game-services-with-cocos2d-js-and-android/
[Tutorial] How to use AdMob with Cocos2d-js and Android http://blog.rhesoft.com/2015/01/27/tutorial-how-to-use-admob-with-cocos2d-js-and-android/
Защита кода:
Если публикуется web версия (html), то код свободно можно посмотреть. Рекомендуются:
Javascript Obfuscator (http://javascriptobfuscator.com/)
JScrambler (https://jscrambler.com)
Портирование для мобильных и PC (native app):
Cocos2D JSB API (http://www.cocos2d-x.org/wiki/Basic_ usage_of_JSB_API): Связь C++ & js
PhoneGap (http://phonegap.com/)
CocoonJS (https://www.ludei.com/cocoonjs/)
Публикация в сети:
Newgrounds (http://www.newgrounds.com/)
Kongregate (http://www.kongregate.com/)
Notdoppler :http://www.notdoppler.com/
Y8 : http://www.y8.com/
Addicting Games : http://www.addictinggames.com/
GameJolt : http://gamejolt.com/
Itch.io : http://itch.io/
MindJolt : http://www.mindjolt.com/
GamesFreak : http://www.gamesfreak.net/
Лицензирование игр:
За лицензировани ваших игр и изменение могут заплатить. Сервис FGL (https://www.fgl.com/) позмоляет найти покупателей.
Форумы об играх:
HTML5GameDevs (http://www.html5gamedevs.com/)
Форумы о разработке игр:
gamedev.net
Маркетинг, продвижение:
1 Социальные сети
twitter,...
2 Форумы
3 Пресса
4 Блоги
5 Реклама
6 Обзоры, стримы
7 Статьи о разработке
8 Портирование и т.п.
Добавление игры в социальные сети. Поддержка новых устройств и т.д.
9 Лицензирование для продажи
10 Выставки, конкурсы, чемпионаты
Cocos2d: Основы
Если игра написана на javascript и запускается в браузере, то используется библиотека cocos2d-js и игра запускается как любое другое веб приложение. Когда игра запускается как приложение (native app), то используется cocos2d-x и код на javascript переводится в быстродействующий код на C с помощью spidermonkey. JSB - связь между js и C++, используйте документированный javascript код (любой спецефичный для браузера код не запустится на native устройстве).
cc.Node - базовый игровой класс наследуемый от cc.Class. От Node наследуется большинство остальных классов: Layer, Scene, Sprite и т.д.
Основы хорошо описаны на http://www.cocos2d-x.org/docs/static-pages/theBasics.html, документацию можно спокойно прочитать с помощью кнопки Next сверху страницы.
Я постараюсь кратко описать основы и поскорее приступить к более сложным и интересным вещам.
Director - Режиссер - управляет сценами
Scene - Сцена - выводится на экран рендером (renderer).
Layer - Слой - находится в сцене (один или несколько), поверх него отображается контент.
Граф сцен - задает порядок вывода элементов на экран по глубине (z-order)
Node - элемент сцены (картинки, меню и т.д.). Сцены также могут включать другие сцены.
В сцену добавляются элементы, например добавление элемента title_node с порядком -2: scene.addChild(title_node, -2);
Спрайты можно создать из картинок var mySprite = new cc.Sprite(res.mySprite_png);
Спраты можно изменять и вращать mySprite.setRotation(40);
Точку относительно которой вращается (и изменяется) объект задает AnchorPoint: mySprite.setAnchorPoint(cc._p(0, 0));
Простые движения можно создать с помощью Action:
var moveBy = new cc.MoveBy(2, cc._p(50,10));
mySprite.runAction(moveBy);
Sequence - последовательность Action, позволяет выполнять несколько Action по очереди:
var moveTo1 = new cc.MoveTo(2, cc._p(50,10));
// ...
mySprite.runAction(Sequence.create(moveTo1, delay, moveBy1, delay.clone(), moveTo2));
Spawn - комбинация Action, позволяет выполнять несколько Action одновременно:
myNode.runAction(Spawn.create(moveTo1, moveBy1, moveTo2));
Лог в консоль выполняется простой командой: cc.log("Position x: " + pos.x + ' y:' + pos.y);
Если вы создали проект cocos2d на javascript (для этого можно выполнить команду "cocos
new
sampleproject
-
l
js"
), то будут созданы папки:
src - исходный код,
res - файлы игры (картинки, музыка и т.д.),
frameworks - папка с игровым движком
project.json - главный конфигурационный файл
mainfest.webapp - конфигурационный файл для web публикации
main.js - файл для запуска игры
Содержимое main.js:
cc.game.onStart = function(){
if(!cc.sys.isNative && document.getElementById("cocosLoading")) //If referenced loading.js, please remove it
document.body.removeChild(document.getElementById("cocosLoading"));
cc.view.enableRetina(cc.sys.os === cc.sys.OS_IOS ? true : false); // Pass true to enable retina display, on Android disabled by default to improve performance
cc.view.adjustViewPort(true); // Adjust viewport meta
cc.view.setDesignResolutionSize(960, 640, cc.ResolutionPolicy.SHOW_ALL); // Setup the resolution policy and design resolution size
cc.view.resizeWithBrowserSize(true);// The game will be resized when browser size change
cc.LoaderScene.preload(g_resources, function () { //load resources
cc.director.runScene(new HelloWorldScene());
}, this);
};
cc.game.run();
Все настроки можно найти в документации http://www.cocos2d-x.org/docs/api-ref/js/v3x/symbols/cc.view.html#constructor
Например, сделаем игру полноэкранной (весь код на https://gist.github.com/derofim/0cf0517ff2b39a48e352):
cc.view.setDesignResolutionSize(cc.view.getFrameSize().width, cc.view.getFrameSize().height, cc.ResolutionPolicy.NO_BORDER);
Ресурсы хранятся в виде (resource.js):
var g_resources = [
"res/a.png",
"res/cocosgui/Marker Felt.ttf",
{
type:"font",
name:"Schwarzwald Regular",
srcs:["res/fonts/Schwarzwald_Regular.eot", "res/fonts/Schwarzwald Regular.ttf"]
}
]
И загружаются в виде (код из main.js):
cc.LoaderScene.preload(g_resources, function () {
cc.director.runScene(new HelloWorldScene());
}, this);
Функция preload позволяет загрузить игровые файлы перед запуском (чтобы сократить время ожидания). По окончанию запуска выполняется функция запускающая первую игровую сцену.
Файл project.json:
{
"project_type": "javascript",
"debugMode" : 1,
"showFPS" : true,
"frameRate" : 60,
"noCache" : false,
"id" : "gameCanvas",
"renderMode" : 0,
"engineDir":"frameworks/cocos2d-html5",
"modules" : ["cocos2d", "extensions"],
"jsList" : [
"src/resource.js",
"src/app.js"
]
}
debugMode: Уровень предупреждений при отладке, от 0 до 6.
showFPS: Показывает или скрывает счетчик fps, значения true или false;
frameRate: Задает fps, для плавной игры рекомендуется 60
id: Элемент html страницы (DOM) для запуска игры (canvas)
engineDir: Папка установки Cocos2d-JS
modules: Библиотеки Cocos2d-JS.
jsList: Массив игровых .js файлов.
Если вам лень задавать массив jsList вручную, то вы можете воспользоваться инструментами вроде https://github.com/mutoo/cocos2d-jsList
Код начальной игровой сцены (задается в main.js):
var HelloWorldLayer = cc.Layer.extend({
ctor:function () {
this._super();
return true;
},
});
var HelloWorldScene = cc.Scene.extend({
onEnter:function () {
this._super();
this.addChild( new HelloWorldLayer() );
}
});
При появлении на сцене вызывается событие onEnter(), есть и другие события, их можно найти в документации http://www.cocos2d-x.org/docs/api-ref/js/v3x/symbols/cc.Node.html#onEnter
Создадим сцену воводящую текст "Hello world" по центру экрана:
var HelloWorldLayer = cc.Layer.extend({
ctor:function () {
this._super();
var size = cc.winSize;
var helloLabel = new cc.LabelTTF("Hello World", "Arial", 38);
helloLabel.x = size.width / 2;// position the label on the center of the screen
helloLabel.y = size.height / 2;
this.addChild(helloLabel, 5);// add the label as a child to this layer
return true;
},
});
Функция addChild позволяет добавить игровой элемент, убрать его можно командой removeChild, например удаление текста через 3 секунды:
var context = this;
setTimeout(function(){
context.removeChild(helloLabel);
}, 3000);
Запустить код можно командой "cocos
run
-
p
web"
Базовые простые фигуры позволяет выводить cc.drawNode:
Cocos2dx how to draw primitives: http://acherkashin.me/2014/11/14/cocos2dx-how-to-draw-primitives/
this.draw=new cc.DrawNode();
this.addChild(this.draw);
this.draw.drawCircle(cc.p(size.width / 2, size.height / 2), 100, 0, 10, false, 6, cc.color(0, 255, 0, 255));
this.draw.drawCircle(cc.p(size.width / 2, size.height / 2), 50, cc.degreesToRadians(90), 50, true, 2, cc.color(0, 255, 255, 255));
Мы нарисовали круг с помощью drawCircle(center, radius, angle, segments, drawLineToCenter, lineWidth, color)
Очистить фигуры в draw можно с помощью this
.
draw
.
clear
();
Пример рисования кривой с помощью drawCardinalSpline(config, tension, segments, lineWidth, color):
var size = cc.winSize;
var centerPos = cc.p(cc.winSize.width / 2, cc.winSize.height / 2);
this.draw=new cc.DrawNode();
this.addChild(this.draw);
var vertices4 = [
cc.p(centerPos.x - 130, centerPos.y - 130),
cc.p(centerPos.x - 130, centerPos.y + 130),
cc.p(centerPos.x + 130, centerPos.y + 130),
cc.p(centerPos.x + 130, centerPos.y - 130),
cc.p(centerPos.x - 130, centerPos.y - 130)
];
this.draw.drawCardinalSpline(vertices4, 0.5, 100, 2, cc.color(255, 255, 255, 255));

this.draw.drawSegment(cc.p(0, 0), cc.p(size.width, size.height), 1, cc.color(255, 0, 255, 255));
this.draw.drawSegment(cc.p(0, size.height), cc.p(size.width, 0), 5, cc.color(255, 0, 0, 255));

this.draw.drawDot(cc.p(size.width / 2, size.height / 2), 40, cc.color(0, 0, 255, 128));
var po = size.width / 2; // position offset
var points = [cc.p(po+60, 60), cc.p(po+70, 70), cc.p(po+60, 70), cc.p(po+70, 60)];
this.draw.drawDots(points, 4, cc.color(0, 255, 255, 255));

//draw.drawPoly(verts, fillColor, lineWidth, color)
var vertices = [cc.p(0, 0), cc.p(50, 50), cc.p(100, 50), cc.p(100, 100), cc.p(50, 100) ];
this.draw.drawPoly(vertices, null, 5, cc.color(255, 255, 0, 255)); // не заполненный
var vertices2 = [cc.p(30, 130), cc.p(30, 230), cc.p(50, 200)];
this.draw.drawPoly(vertices2, null, 2, cc.color(255, 0, 255, 255)); // не заполненный
var vertices3 = [cc.p(60, 130), cc.p(60, 230), cc.p(80, 200)];
this.draw.drawPoly(vertices3, cc.color(0, 255, 255, 50), 2, cc.color(255, 0, 255, 255)); // заполненный

//draw.drawRect(origin, destination, fillColor, lineWidth, lineColor)
draw.drawRect(cc.p(120, 120), cc.p(200, 200), null, 2, cc.color(255, 0, 255, 255)); // not fill
draw.drawRect(cc.p(120, 220), cc.p(200, 300), cc.color(0, 255, 255, 180), 2, cc.color(128, 128, 0, 255)); // fill

// draw.drawQuadBezier(origin, control, destination, segments, lineWidth, color)
var centerPos = cc.p(size.width / 2, size.height / 2);
this.draw.drawQuadBezier(cc.p(0, size.height), cc.p(centerPos.x, centerPos.y), cc.p(size.width, size.height), 50, 2, cc.color(255, 0, 255, 255));
https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves
// draw.drawCubicBezier(origin, control1, control2, destination, segments, lineWidth, color)
this.draw.drawCubicBezier(
cc.p(size.width / 2, size.height / 2), cc.p(size.width / 2 + 30, size.height / 2 + 50),
cc.p(size.width / 2 + 60, size.height / 2 - 50), cc.p(size.width, size.height / 2), 100,
2, // lineWidth
cc.color(255, 0, 255, 255) // color
);
Пример задания своей кривой http://cubic-bezier.com/#.17,.67,.63,1.41
Элементы
Node - базовый элемент в cocos2d.
Глубина или z-order определяет положение элемента над остальными, и тем самым, задает видимость элемента.
nodeName.setLocalZOrder( 5 ); // задает глубину на сцене
nodeName.getLocalZOrder(); // узнает глубину на сцене
nodeName.setGlobalZOrder( 5 ); // задает порядок отрисовки
nodeName.getGlobalZOrder( ); // узнает порядок отрисовки
Элементы можно масштабировать:
nodeName.setScaleX( 0.5 ); // задает ширину элемента и его потомков
nodeName.getScaleX( ); // узнает ширину элемента и его потомков
nodeName.setScaleY( 0.5 ); // задает высоту элемента и его потомков
nodeName.getScaleY( ); // узнает высоту элемента и его потомков
nodeName.setScaleZ( 0.5 ); // задает глубину элемента и его потомков
nodeName.getScaleZ( ); // узнает глубину элемента и его потомков
nodeName.setScale( 0.5 ); // задает ширину и высоту элемента и его потомков
nodeName.getScale( ); // узнает ширину и высоту элемента и его потомков
nodeName.setScale( 0.5, 0.2 ); // задает ширину и высоту элемента и его потомков
Масштаб по умолчанию равен 1. Физические элементы нельзя масштабировать таким способом.
Позиция элемента:
nodeName.setPosition( cc.p( 50, 100 ) ); // задает позицию
nodeName.getPosition( ); // узнает позицию
result.x; // узнает позицию по оси x
result.y; // узнает позицию по оси y
nodeName.setPosition( 50.0, 100.0 ); // задает позицию
// задает позицию по оси
nodeName.setPositionX( 50.0 );
nodeName.setPositionY( 50.0 );
// узнает позицию по оси
nodeName.getPositionX( );
nodeName.getPositionY( );
Наклон спрайта - shear distortion (Физические элементы нельзя изменять таким способом):
nodeName.setSkewX( 10.0 );
nodeName.setSkewY( 10.0 );
nodeName.getSkewX( );
nodeName.getSkewY( );
Точка привязки - anchor point - точка относительно которой происходят изменения.
nodeName.setAnchorPoint( cc.p( 0.5, 0.5 ) );
nodeName.getAnchorPoint();
nodeName.getAnchorPointInPoints( ); // получает значение в пикселях
Размер элемента:
nodeName.setContentSize( cc.size( 30.0, 90.0 ) );
nodeName.getContentSize( );
Пример использования:
var layerGradient = new cc.LayerGradient( cc.color.ORANGE, new cc.Color(0,255,0,100), cc.p(0, -1) ); // layerGradient(Starting color, Ending color, Gradient direction)
layerGradient.setContentSize(cc.size(size.width / 2, size.height / 2));
layerGradient.setPosition(cc.p(size.width / 4, size.height / 4));
this.addChild(layerGradient);
Видимость элемента:
nodeName.setVisible( true );
nodeName.isVisible( );
Вращение:
nodeName.setRotation( 45.0 );
nodeName.getRotation( );
// задать вращение в градусах
nodeName.setRotationX( 40.0 );
nodeName.setRotationY( 60.0 );
// узнать вращение в градусах
nodeName.getRotationX( );
nodeName.getRotationY( );
Идентификация элемента:
nodeName.setTag( 5 );
nodeName.getTag( );
nodeName.setName( "Name" );
nodeName.getName( );
Получение сцены (только для C++):
nodeName->getScene( );
Получение прямоугольника для проверки столкновений:
nodeName.getBoundingBox( );
Действия:
nodeName.runAction( actionToRun );
nodeName.stopAllActions( );
nodeName.stopAction( actionToStop );
nodeName.stopActionByTag( 5 );
nodeName.getActionByTag( );
Прозрачность и цвет:
nodeName.setOpacity( 100 );
nodeName.getOpacity( );
nodeName.setColor( new cc.Color(red, green, blue, alpha) );
nodeName.getColor( );
Добавление и получение:
this.addChild( nodeName ); // добавить с z-index = 0
this.addChild( nodeName, 2 ); // добавить с z-index
this.addChild( nodeName, 2, 65 ); // добавить с z-index и tag
this.getChildByTag( 2 );
this.getChildByName( "Name of node" );
Удаление:
nodeName.removeFromParent( ); // удаляет и очищает действия
nodeName.removeFromParentAndCleanup( false ); // удаляет и может очистить действия
this.removeChild( nodeName ); // удаляет и очищает действия
this.removeChild( nodeName, false ); // удаляет и может очистить действия
this.removeChildByTag( 6 ); // удаляет по тегу и очищает действия
this.removeChildByTag( 6, false );
this.removeAllChildren( );
this.removeAllChildrenWithCleanup( false )
Спрайты
Спрайт из одного изображения (определен в resource.js):
var size = cc.winSize;
this.sprite = new cc.Sprite(res.HelloWorld_png);
this.sprite.attr({ x: size.width / 2, y: size.height / 2 });
this.addChild(this.sprite, 0);
Спрайт из таблицы спрайтов (Sprite Sheet), пример файла http://img13.deviantart.net/7fc3/i/2012/288/4/7/volt_sprite_sheet_by_kwelfury-d5hx008.png :
var size = cc.winSize;
this.sprite = new cc.Sprite(res.volt_sprite_sheet_png,cc.rect(40,30,150,200));
this.sprite.attr({ x: size.width / 2, y: size.height / 2 });
this.addChild(this.sprite, 0);
Анимация спрайтов:
var size = cc.winSize;
var walk01 = cc.rect(0,0,72,97);
var walk02 = cc.rect(73,0,72,97);
var walk03 = cc.rect(146,0,72,97);
var walk04 = cc.rect(0,98,72,97);
var walk05 = cc.rect(73,98,72,97);
var walk06 = cc.rect(146,98,72,97);
var walk07 = cc.rect(219,0,72,97);
var walk08 = cc.rect(292,0,72,97);
var walk09 = cc.rect(219,98,72,97);
var walk10 = cc.rect(365,0,72,97);
var walk11 = cc.rect(292,98,72,97);
var frameDatas=[walk01,walk02,walk03,walk04,walk05,walk06,walk07,walk08,walk09,walk10,walk11];
var texture = cc.textureCache.addImage(res.Sprite_Sheet);
var animFrames=[];
for(var index in frameDatas)
{
var spriteFrame = new cc.SpriteFrame(texture, frameDatas[index]);
var animFrame = new cc.AnimationFrame();
animFrame.initWithSpriteFrame(spriteFrame, 1, null);
animFrames.push(animFrame);
}
this.sprite = new cc.Sprite();
this.sprite.attr({ x: size.width / 2,y: size.height / 2 });
this.addChild(this.sprite, 0);
var spAnim = new cc.Animation(animFrames, 0.08);
var animate = new cc.Animate(spAnim);
this.sprite.runAction(animate.repeatForever());
Анимация с помощью PLIST (используя программу texturepacker)
Рекомендуется к прочтению https://www.codeandweb.com/blog/2015/12/15/animations-and-spritesheets-in-cocos2d-x
Чтобы разбить готовый sprite sheet на несколько изображений используйте https://github.com/ForkandBeard/Alferd-Spritesheet-Unpacker
Полученные несколько изображений можно оптимизировать в texturepacker сохранив снова в один файл.
Бесплатно создать sprite sheet можно и в cocos studio, что рекомендуется: http://www.cocos2d-x.org/docs/editors_and_tools/chapter2/SpriteSheet/en/index.html
Мои настройки (заметьте старый формат при сохранении):
Код анимации:
var res = {
HelloWorld_png : "res/HelloWorld.png",
//...
sheet_plist : "res/sheet.plist",
sheet_png : "res/sheet.png",
};
var size = cc.winSize;
cc.spriteFrameCache.addSpriteFrames(res.sheet_plist, res.sheet_png);
var animFrames=[];
for(var i=1;i<10;i++)
{
var str = i+".png";
var spriteFrame=cc.spriteFrameCache.getSpriteFrame(str);
animFrames.push(spriteFrame);
}
this.sprite = new cc.Sprite();
this.sprite.attr({ x: size.width / 2,y: size.height / 2 });
this.addChild(this.sprite, 0);
var spAnim = new cc.Animation(animFrames, 0.08);
var animate = new cc.Animate(spAnim);
this.sprite.runAction(animate.repeatForever());
Функции работы со спрайтами:
var coolSprite = new cc.Sprite( "Image Filename.png" ); // создание
var coolSprite = new cc.Sprite( res.CloseNormal_png, cc.Rect( 0, 0, 100, 50 ) ); // обрезка
// SPRITE FRAME CACHE - sprite sheet
cc.spriteFrameCache.addSpriteFrames( "spriteSheet.plit" );
var coolSprite = new cc.Sprite( spriteFrameCache.getSpriteFrame( "coolFrameName.png" ) );
// SPRITE FRAME
cc.spriteFrameCache.addSpriteFrames( "spriteSheet.plit" );
var coolSprite = new cc.Sprite( "coolFrameName.png" );
// textureCache
cc.textureCache.addImage( "Cool Image.png" );
var coolSprite = new cc.Sprite( "Cool Image.png" );
cc.textureCache.addImage( "Cool Image.png" );
var coolSprite = new cc.Sprite( "Cool Image.png", cc.Rect( 0, 0, 100, 50 ) );
coolSprite.setSpriteFrame( spriteFrameCache.getSpriteFrame( "coolFrameName.png" ) );
coolSprite.setSpriteFrame( "coolFrameName.png" );
// Texture
coolSprite.getTexture( );
coolSprite.setTexture( "coolImage.png" );
coolSprite.setTexture( texture );
coolSprite.getTextureRect( );
coolSprite.setTextureRect( cc.Rect( 0, 0, 100, 50 ) );
Ресурсы где можно найти бесплатные элементы дизайна для игр:
1) http://opengameart.org/latest
2) http://www.gameartguppy.com/product-category/free-game-art-sprites/
4) Тайлы http://untamed.wild-refuge.net/rpgxp.php
5) 8 bit https://crateboy.itch.io/crateboy-2007-2014
6) http://bagfullofwrong.co.uk/bagfullofwords/abuse-my-ip-make-games/
7) http://www.gameart2d.com/freebies.html
8) http://www.widgetworx.com/spritelib/
9) http://www.glitchthegame.com/public-domain-game-art/
10) http://www.dumbmanex.com/bynd_freestuff.html
11) http://www.reinerstilesets.de/
13) http://www.roencia.com/index.html
14) http://blogoscoped.com/archive/2006-08-08-n51.html
15) http://www.lostgarden.com/search/label/free%20game%20graphics
16) http://subtlepatterns.com/
17) https://openclipart.org/
Работа со сценами
Создание сцены:
var HelloWorldLayer = cc.Layer.extend({
ctor:function () {
this._super();
return true;
}
});
var HelloWorldScene = cc.Scene.extend({
onEnter:function () {
this._super();
var layer = new HelloWorldLayer();
this.addChild(layer);
}
});
Запуск первой игровой сцены:
var scene = new NewScene( );
cc.director.runScene( scene );
Добавление текущей сцены:
cc.director.pushScene( scene );
Снятие текущей сцены:
cc.director.popScene( );
Замена сцены:
cc.director.replaceScene( scene );
Эффекты появления сцены:
cc.director.runScene( new cc.TransitionCrossFade( 1.0, new HelloWorldScene() ) );
Все эффекты можно увидеть на примере [ assets/demo/cocos-translations ]
Код примера: https://gist.github.com/derofim/034db9530f32ed875e05
Список эффектов:
cc.TransitionCrossFade
cc.TransitionFade
cc.TransitionFadeTR
cc.TransitionJumpZoom
cc.TransitionMoveInL
cc.TransitionPageTurn
cc.TransitionProgress
cc.TransitionRotoZoom
cc.TransitionSceneOriented
cc.TransitionShrinkGrow
cc.TransitionSlideInL
cc.TransitionSplitCols
cc.TransitionTurnOffTiles
Также существуют функции принимающие несколько аргументов:
cc.TransitionSceneOriented(time,scene,orientation);
cc.TransitionFlipAngular(time,scene,orientation);
cc.TransitionFlipX(t,scene,o);
cc.TransitionFlipY(t,scene,o);
cc.TransitionZoomFlipAngular(time,scene,o);
cc.TransitionZoomFlipX(time,scene,o);
cc.TransitionZoomFlipY(time,scene,o);
Последний параметр у этих функций может принимать значения
cc.TRANSITION_ORIENTATION_LEFT_OVER
cc.TRANSITION_ORIENTATION_RIGHT_OVER
cc.TRANSITION_ORIENTATION_UP_OVER
cc.TRANSITION_ORIENTATION_DOWN_OVER
Если сцену нужно заполнить сплошным цветом, то можно использовать new cc.LayerColor(cc.color(r, g ,b, a));
Можно заполнить участок сцены цветом т.е. добавить цветной прямоугольник:
var colorSprite = new cc.Sprite();
colorSprite.setTextureRect(cc.rect(0, 0, 200, 100)); // x y w h
colorSprite.setColor(cc.color(100,200,200)); // r g b
colorSprite.setNormalizedPosition(0.5,0.6); // 0..1
this.addChild(colorSprite);
/
Действия
"To" действия изменяют элемент вне зависимости от его свойств.
"By"действия изменяют элемент относительно от его свойств.
var nodeAction = new cc.MoveTo( 2, cc.p( 50, 100 ) );
nodeName.runAction( nodeAction );
var nodeAction = new cc.MoveBy( 2, cc.p( 50, 100 ) );
nodeName.runAction( nodeAction );
Подробнее можно прочитать на http://cocos.sonarlearning.co.uk/docs/actions
Функции:
// Прыжки
// y - Необязательный параметр
cc.JumpTo(duration, position, y, height, jumps)
cc.JumpBy(duration, position, y, height, jumps)
// Движение по кривой, передается время и массив точек
var bezierOptions = [cc.p( 0, 400 ), cc.p( 300, 50 ), cc.p( 100, 100 )];
var nodeAction = new cc.BezierTo( 2, bezierOptions );
nodeName.runAction( nodeAction );
// cc.CardinalSplineTo(duration, points, tension)
// Cardinal Spline path - http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Cardinal_spline Absolute coordinates
var action = cc.cardinalSplineTo(3, array, 0);
var action = cc.cardinalSplineTo(3, array, 0);
var nodeAction = new cc.BezierBy( 2, bezierOptions );
nodeName.runAction( nodeAction );
// Мгновенное перемещение
var nodeAction = new cc.Place( cc.p( 200, 150 ) );
nodeName.runAction( nodeAction );
// Масштабирование
var nodeAction = new cc.ScaleTo( 2, 3, 6 );
nodeName.runAction( nodeAction );
var nodeAction = new cc.ScaleBy( 2, 3, 6 );
nodeName.runAction( nodeAction );
// Вращение
var nodeAction = new cc.RotateTo( 2, 45 );
nodeName.runAction( nodeAction );
var nodeAction = new cc.RotateBy( 2, 45 );
nodeName.runAction( nodeAction );
// Изменение цвета
var nodeAction = new cc.TintTo( 2, 45, 45, 100 );
nodeName.runAction( nodeAction );
var nodeAction = new cc.TintBy( 2, 45, 45, 100 );
nodeName.runAction( nodeAction );
// Изменение прозрачности
var nodeAction = new cc.FadeTo( 2, 100 ); // получает время, прозрачность
nodeName.runAction( nodeAction );
var nodeAction = new cc.FadeIn( 2 ); // получает время, прозрачность станет = 255
nodeName.runAction( nodeAction );
var nodeAction = new cc.FadeOut( 2 ); получает время, прозрачность станет = 0
nodeName.runAction( nodeAction );
// Наклон (t, sx, sy)
var nodeAction = new cc.SkewTo( 2, 20, 20 );
nodeName.runAction( nodeAction );
var nodeAction = new cc.SkewBy( 2, 20, 20 );
nodeName.runAction( nodeAction );
// Задержка в секундах
var delayAction = new cc.DelayTime( 2.5 );
// Последовательное выполнение действий
var sequenceAction = new cc.Sequence( nodeAction1, nodeAction2, nodeAction3 );
nodeName.runAction( sequenceAction );
// Последовательное выполнение действий в обратном порядке
var sequenceAction = new cc.Sequence( nodeAction1, nodeAction2, nodeAction3 );
nodeName.runAction( sequenceAction.reverse( ) );
// Повтор дейсвтия
var repeatAction = new cc.Repeat( actionToRepeat, 5 ); // 5 раз
nodeName.runAction( repeatAction );
var repeatForeverAction = new cc.RepeatForever( actionToRepeat ); // вечно
nodeName.runAction( repeatForeverAction );
// Одновременное выполнение действий
var spawnAction = new cc.Spawn( nodeAction1, nodeAction2, nodeAction3 );
nodeName.runAction( spawnAction );
// Вызов функции при выполнении действия
var functionAction = new cc.CallFunc( functionToCall );
nodeName.runAction( functionAction );
Действиям можно назначить функции по завершению:
var seq=new cc.Sequence(action1,function(){/*action1 end callback*/}, action2,function(){/*action2 end callback*/});
Анимация спрайтов:
var animation = new cc.Animation(spriteFrames, 0.08);
this.sprite.runAction(cc.animate(animation).repeatForever()); //or new cc.Animation(..) patten can be used.
До этого мы уже использовали код анимации:
var animate = new cc.Animate(spAnim);
this.sprite.runAction(animate.repeatForever());
Вспомогательные действия (удобны при создании меню и физики):
Можно создавать в виде:
var easing=cc.easeBackIn(); // или
var easing=new cc.EaseBackIn();
action.easing(easing);
Функции:
var nodeAction = new cc.MoveBy( 2.0, cc.p( 300, 0 ) ); // Пример действия
var easeAction = new cc.EaseBackIn( nodeAction ); // Пример easeAction
nodeName.runAction( easeAction );
var easeAction = new cc.EaseBackInOut( nodeAction );
var easeAction = new cc.EaseBackOut( nodeAction );
var easeAction = new cc.EaseBounceIn( nodeAction );
var easeAction = new cc.EaseBounceInOut( nodeAction );
var easeAction = new cc.EaseBounceOut( nodeAction );
var easeAction = new cc.EaseCircleActionIn( nodeAction );
var easeAction = new cc.EaseCircleActionInOut( nodeAction );
var easeAction = new cc.EaseCircleActionOut( nodeAction );
var easeAction = new cc.EaseCubicActionIn( nodeAction );
var easeAction = new cc.EaseCubicActionInOut( nodeAction );
var easeAction = new cc.EaseCubicActionOut( nodeAction );
var easeAction = new cc.EaseElasticIn( nodeAction );
var easeAction = new cc.EaseElasticInOut( nodeAction );
var easeAction = new cc.EaseElasticOut( nodeAction );
var easeAction = new cc.EaseExponentialIn( nodeAction );
var easeAction = new cc.EaseExponentialInOut( nodeAction );
var easeAction = new cc.EaseExponentialOut( nodeAction );
var easeAction = new cc.EaseIn( nodeAction, 5.0 );
var easeAction = new cc.EaseInOut( nodeAction, 5.0 );
var easeAction = new cc.EaseOut( nodeAction, 5.0 );
var easeAction = new cc.EaseQuadraticActionIn( nodeAction );
var easeAction = new cc.EaseQuadraticActionInOut( nodeAction );
var easeAction = new cc.EaseQuadraticActionOut( nodeAction );
var easeAction = new cc.EaseQuarticActionIn( nodeAction );
var easeAction = new cc.EaseQuarticActionInOut( nodeAction );
var easeAction = new cc.EaseQuarticActionOut( nodeAction );
var easeAction = new cc.EaseQuinticActionIn( nodeAction );
var easeAction = new cc.EaseQuinticActionInOut( nodeAction );
var easeAction = new cc.EaseQuinticActionOut( nodeAction );
var easeAction = new cc.EaseSineIn( nodeAction );
var easeAction = new cc.EaseSineInOut( nodeAction );
var easeAction = new cc.EaseSineOut( nodeAction );
Подробнее можно прочитать на http://www.cocos2d-x.org/wiki/Actions
Меню & UI
Не забудьте добавить extensions в project.json
"modules" : ["cocos2d", "extensions"],
Также возможно примется обновить .h и .cpp файлы, а также Android.mk как написано в инструкции http://cocos.sonarlearning.co.uk/docs/ui-elements-such-as-uibutton
Бесплатные шрифты: http://v-play.net/2015/11/best-free-fonts-for-mobile-development/
Label - используется для вывода текста.
LabelTTF - TrueType формат создан для хранения информации o контуре символа (векторное изображение). В отличие от растрового символа — векторный легко масштабируется.
var label= cc.LabelTTF("текст", "шрифт.ttf", размер, пропорции, hAlignment, vAlignment);
// [cc.Size] пропорции - dimensions- отвечают ширине и высоте, если не указаны вычисляются автоматмчески.
// hAlignment - горизонтальное положение текста {cc.TEXT_ALIGNMENT_LEFT | cc.TEXT_ALIGNMENT_CENTER | cc.TEXT_ALIGNMENT_RIGHT}
// vAlignment - вертикальное положение текста {cc.VERTICAL_TEXT_ALIGNMENT_TOP|cc.VERTICAL_TEXT_ALIGNMENT_CENTER|cc.VERTICAL_TEXT_ALIGNMENT_BOTTOM}
- Поддерживаются также и системные шрифты. cc.LabelTTF может быть медленным при js-binding на мобильных устройствах. Функции для работы с LabelTTF можно посмотреть в документации http://www.cocos2d-x.org/docs/api-ref/js/v3x/symbols/cc.LabelTTF.html
LabelBMFont - позволяет загружать растровые шрифты .fnt. Каждый символ является cc.Sprite, а значит может быть повернут, перемещен и т.д. Рекомендуется к использованию.
var label = cc.LabelBMFont(text, fntFile, width, alignment, imageOffset)
// [string] text - текст для вывода.
// [fntFile] fntFile - шрифт.fnt
// [width] width - ширина label.
// alignment - горизонтальное положение {cc.TEXT_ALIGNMENT_LEFT|cc.TEXT_ALIGNMENT_CENTER|cc.TEXT_ALIGNMENT_RIGHT}
// [cc.point] imageOffset - xy оффсет выравнивая центра изображения.
Можно использовать редакторы:
http://glyphdesigner.71squared.com/ (Commercial, Mac OS X)
http://www.n4te.com/hiero/hiero.jnlp (Free, Java)
http://slick.cokeandcode.com/demos/hiero.jnlp (Free, Java)
http://www.angelcode.com/products/bmfont/ (Free, Windows only)
Примеры:
var label1 = new cc.LabelBMFont("Test case", "test.fnt");
var label2 = new cc.LabelBMFont("test case", "test.fnt", 200, cc.TEXT_ALIGNMENT_LEFT);
var label3 = new cc.LabelBMFont("This is a \n test case", "test.fnt", 200, cc.TEXT_ALIGNMENT_LEFT, cc.p(0,0));
Пример использования разных шрифтов (файлы можно скачать с https://github.com/nutcrackify/Rapid_Game_Development_Using_Cocos2d-js/tree/master/res):
var counter = 0;
HelloWorldLayer = cc.Layer.extend({
ctor:function () {
this._super();
// var res = { BM_Font_png : "res/bitmapFontTest.png", BM_Font : "res/bitmapFontTest.fnt", Abduction_ttf : "res/Abduction.ttf", };
var layer = new cc.LayerColor(cc.color(255, 0 , 100, 255));
this.addChild(layer);
var size = cc.winSize;
this.Label1 = new cc.LabelTTF('Default Font Label','', 32);
this.Label1.attr({
x: size.width / 2,
y: size.height / 1.3
});
this.addChild(this.Label1);
this.Label2 = new cc.LabelTTF('Custom Font Label','Abduction', 32);
this.Label2.attr({
x: size.width / 2,
y: size.height / 1.5
});
this.addChild(this.Label2);
this.Label3 = new cc.LabelTTF('Label With Stroke','Abduction', 32);
this.Label3.attr({
x: size.width / 2,
y: size.height / 1.9
});
this.Label3.enableStroke(cc.color(0,0,0),10);
this.addChild(this.Label3);
this.Label4 = new cc.LabelTTF('Label With Shadow','Abduction', 32);
this.Label4.attr({
x: size.width / 2,
y: size.height / 2.3
});
this.Label4.enableShadow(cc.color(0,0,0), 50, 50);
this.addChild(this.Label4);
this.Label5 = new cc.LabelBMFont("Bitmap Font", res.BM_Font);
this.Label5.attr({
x: size.width / 2,
y: size.height / 2.9
});
this.addChild(this.Label5);
return true;
},
});
var HelloWorldScene = cc.Scene.extend({
onEnter:function () {
this._super();
this.addChild( new HelloWorldLayer() );
}
});

Меню:
cc.Menu - базовый класс. В него нужно добавлять addChild остальных.
cc.MenuItem - должен быть потомком cc.Menu.
MenuItemLabel - поддерживает cc.Label
var menuitemLabel = new cc.MenuItemLabel(label,selector,target);
// [cc.Label] label - экземпляр cc.Label.
// [string] selector - callback функция нажатия.
// [object] target - обьект содержащий callback функцию.
MenuItemImage - поддерживает изображения
var menuItem = new cc.MenuItemImage(normalImage, selectedImage, disabledImage, selector, target);
// [string] normalImage - изображение стандартного состояния.
// [string] selectedImage - изображение выбранного состояния.
// [string] disabledImage - изображение отключенного состояния.
// [string] selector - callback функция нажатия.
// [object] target - объект с функцией.
Пример:
var counter = 0;
HelloWorldLayer = cc.Layer.extend({
ctor:function () {
this._super();
var size = cc.winSize;
var layer = new cc.LayerColor(cc.color(255, 0 , 100, 255));
this.addChild(layer);
this.Menu=new cc.Menu();
this.Menu.attr({ x: 0, y: 0 });
var label=new cc.LabelTTF('MenuItem with label',36);
this.MenuItem1 = new cc.MenuItemLabel(label,'onMenuClicked',this);
this.MenuItem1.attr({ x: size.width / 2, y: size.height / 1.3 });
this.Menu.addChild(this.MenuItem1);
// var res = { /* .. */ MenuItemImage_Normal:"res/menuitem_normal.png", MenuItemImage_Selected:"res/menuitem_selected.png", };
this.MenuItem2 = new cc.MenuItemImage(res.MenuItemImage_Normal,res.MenuItemImage_Selected,null,'onMenuClicked',this);
this.MenuItem2.attr({ x: size.width / 2, y: size.height / 1.8 });
this.Menu.addChild(this.MenuItem2);
this.addChild(this.Menu);
return true;
},
onMenuClicked:function(){
}
});
var HelloWorldScene = cc.Scene.extend({
onEnter:function () {
this._super();
this.addChild( new HelloWorldLayer() );
}
});
Функции:
var menuItem = new cc.MenuItemAtlasFont( "TextToDisplay", "AtlasImageFilePath", 25, 25, functionToCall );
var menuItem = new cc.MenuItemFont( "TextToDisplay", functionToCall );
var menuItem = new cc.MenuItemImage( "normalImageFilePath", "selectedImageFilePath", "disabledImageFilePath", functionToCall );
var menuItem = new cc.MenuItemLabel( label, functionToCall );
var menuItem = new cc.MenuItemSprite( normalSprite, selectedSprite, disabledSprite, functionToCall );
var menu = new cc.Menu( menuItem1, menuItem2, menuItem3 );
menuItem.setFontName( "pathOfNewFontFile" );
menuItem.setFontSize( 5 );
Подробнее на http://cocos.sonarlearning.co.uk/docs/menus
Текст
// atlas
var label = new cc.LabelAtlas( "String to display", "Filepath of the character map", 5, 5 );
// bitmap
var label = new cc.LabelBMFont( "String to display", "Filepath of the bmfont" );
// TrueType
var label = new cc.LabelTTF( "String to display", "Font filepath" );
Пример atlas (файлы можно скачать из https://gist.github.com/derofim/93a1c459752f7406f4f7 ):
var res = {
tuffy_png : "res/tuffy.png",
tuffy_plist : "res/tuffy.plist",
};
//...
var label = new cc.LabelAtlas( "String to display", res.tuffy_plist );
Пример ttf:
var size = cc.winSize;
var myLabelTTF = new cc.LabelTTF('label text', 'Times New Roman', 32, cc.size(320,32), cc.TEXT_ALIGNMENT_LEFT);
myLabelTTF.setPosition( cc.p( size.width / 2+150, 200 ) );
this.addChild(myLabelTTF, 0);
var fontDef = new cc.FontDefinition();
fontDef.fontName = "Arial";
fontDef.fontSize = "32";
var myLabel = new cc.LabelTTF('label text', fontDef);
myLabel.setPosition( cc.p( size.width / 2+50, 100 ) );
this.addChild(myLabel, 0);
UI:
Кнопка:
// создание кнопки
var button = new ccui.Button();
button.loadTextures( "Normal image filepath", "Selected image filepath" );
// создание событий нажатия
button.addTouchEventListener( this.touchEvent, this );
touchEvent: function(sender, type)
{
switch (type)
{
case ccui.Widget.TOUCH_BEGAN:
// ...
break;
case ccui.Widget.TOUCH_MOVED:
// ...
break;
case ccui.Widget.TOUCH_ENDED:
// ...
break;
case ccui.Widget.TOUCH_CANCELLED:
// ...
break;
}
}
CheckBox:
var checkBox = new ccui.CheckBox( );
checkBox.loadTextures( "Checkbox normal image file path",
"Checkbox normal pressed image filepath",
"Checkbox active image filepath",
"Checkbox normal disabled image filepath",
"Checkbox active disabled image filepath" );
checkBox.addEventListener( this.selectedStateEvent, this );
selectedStateEvent: function ( sender, type )
{
switch ( type )
{
case ccui.CheckBox.EVENT_SELECTED:
break;
case ccui.CheckBox.EVENT_UNSELECTED:
break;
default:
break;
}
}
Еще элементы:
// UIIMAGEVIEW
var imageView = new ccui.ImageView( );
imageView.loadTexture( "Image filepath" );
// UILAYOUT
var layout = new ccui.Layout();
layout.setLayoutType( ccui.Layout.LINEAR_HORIZONTAL );
layout.sizeType = ccui.Widget.SIZE_PERCENT;
layout.setSizePercent( cc.p( 0.5, 0.5 ) );
layout.setPositionType( ccui.Widget.POSITION_PERCENT );
layout.setPositionPercent( cc.p( 0.25, 0.25 ) );
layout.setBackGroundColorType( ccui.Layout.BG_COLOR_SOLID );
layout.setBackGroundColor( cc.color.GRAY );
layout.addChild( nodeToAdd );
// UILISTVIEW
var listView = new ccui.ListView( );
listView.setDirection( ccui.ScrollView.DIR_VERTICAL );
listView.setTouchEnabled( true );
listView.setBounceEnabled( true );
listView.setBackGroundImage( "Background image filepath" );
listView.pushBackCustomItem( customItem );
listView.addEventListener(this.selectedItemEvent, this);
selectedItemEvent: function ( sender, type )
{
switch ( type )
{
case ccui.ListView.EVENT_SELECTED_ITEM:
cc.log( "select child index = " + sender.getCurSelectedIndex( ) );
break;
default:
break;
}
}
// UILOADINGBAR
var loadingBar = new ccui.LoadingBar( );
loadingBar.loadTexture( "Loading bar image filepath" );
loadingBar.setPercent( 0 ); // 0 - 100
//UIPAGEVIEW
var pageView = new ccui.PageView( );
pageView.setTouchEnabled( true );
pageView.setContentSize( cc.size( 240.0, 900.0 ) );
pageView.setAnchorPoint( cc.p( 0.5, 0.5 ) );
var layout = new ccui.Layout( );
layout.setContentSize( cc.size( 240.0, 900.0 ) );
layout.addChild( elementToAddToLayout );
pageView.addPage( layout );
pageView.insertPage( layout, 1 );
pageView.removePageAtIndex( 0 );
pageView.removeAllPages( );
pageView.scrollToPage( 2 );
pageView.addEventListener( this.pageViewEvent, this );
pageViewEvent: function ( sender, type )
{
switch ( type )
{
case ccui.PageView.EVENT_TURNING:
var pageView = sender;
cc.log( "Current page " + pageView.getCurPageIndex( ) );
break;
default:
break;
}
}
// UIRICHTEXT
var richText = new ccui.RichText( );
richText.ignoreContentAdaptWithSize( false );
richText.width = 120;
richText.height = 100;
var re1 = new ccui.RichElementText( 1, cc.color.WHITE, 255, "This color is white", "Font filepath", 10 );
var re2 = new ccui.RichElementText( 2, cc.color.YELLOW, 255, "And this is yellow. ", "Font filepath", 10 );
richText.pushBackElement( re1 );
richText.pushBackElement( re2 );
// UISCROLLVIEW
var scrollView = new ccui.ScrollView( );
scrollView.setDirection( ccui.ScrollView.DIR_VERTICAL );
scrollView.setTouchEnabled( true );
scrollView.setBounceEnabled( true );
scrollView.setBackGroundImage( "Background image filepath" );
scrollView.setContentSize( cc.size( 300, 200 ) );
scrollView.setInnerContainerSize( cc.size( 1280, 2500 ) );
scrollView.setAnchorPoint( cc.p( 0.5, 0.5 ) );
scrollView.addChild( nodeToAdd );
// UISLIDER
var slider = new ccui.Slider( );
slider.setTouchEnabled( true );
slider.loadBarTexture( "Slider background" );
slider.loadSlidBallTextures( "Slider button", "Slider button clicked", "" );
slider.loadProgressBarTexture( "Progress bar" );
slider.addEventListener( this.sliderEvent, this );
sliderEvent: function( sender, type )
{
switch (type)
{
case ccui.Slider.EVENT_PERCENT_CHANGED:
cc.log("Percent " + sender.getPercent().toFixed(0));
break;
}
}
// UITEXT
var text = new ccui.Text( );
text.setString( "String to display" );
text.setFontName( "Font filepath" );
text.setFontSize( 32 );
text.setTextHorizontalAlignment( cc.TEXT_ALIGNMENT_CENTER );
text.setTextVerticalAlignment( cc.VERTICAL_TEXT_ALIGNMENT_CENTER );
// UITEXTATLAS
var textAtlas = new ccui.TextAtlas( );
textAtlas.setProperty( "String to display", "Character map filepath", 5, 5, "2" );
// UITEXTBMFONT
var labelBMFont = new ccui.TextBMFont( );
labelBMFont.setFntFile( "Bitmap font filepath" );
labelBMFont.setString( "String to display" );
// UITEXTFIELD
var textField = new ccui.TextField( );
textField.setTouchEnabled( true );
textField.fontName = "Font filepath";
textField.placeHolder = "Input text here";
textField.fontSize = 30;
textField.setMaxLengthEnabled( true );
textField.setMaxLength( 12 );
textField.setPasswordEnabled( true );
textField.setPasswordStyleText( "*" );
textField.addEventListener( this.textFieldEvent, this );
textFieldEvent: function( sender, type )
{
switch ( type )
{
case ccui.TextField.EVENT_ATTACH_WITH_IME:
break;
case ccui.TextField.EVENT_DETACH_WITH_IME:
break;
case ccui.TextField.EVENT_INSERT_TEXT:
cc.log( "%s", textField.string );
break;
case ccui.TextField.EVENT_DELETE_BACKWARD:
cc.log( "%s", textField.string );
break;
}
}
// UIVIDEOPLAYER
var videoPlayerField = new ccui.VideoPlayer(res.applause_mp4);
this.addChild(videoPlayerField);
videoPlayerField.x = size.width / 2;
videoPlayerField.y = size.height / 2;
videoPlayerField.play();
// UIWEBVIEW
var webview = new ccui.WebView ("http://localhost:9000/cocos2d.html#menu");
webview.setContentSize(400, 400);
webview.setPosition( cc.p( size.width / 2, size.height / 2 ) );
this.addChild(webview);
3d графика
3d только для нативных платформ.
Пример загрузки спрайта на мобильном устройстве/компьютере (файлы можно скачать с https://github.com/mmvlad/simplerpgcocos/tree/master/Resources/Sprite3DTest):
if (cc.sys.isNative) { // no web support
var sprite3d = new jsb.Sprite3D(res.boss_obj);
sprite3d.setTexture(res.boss_png);
this.addChild(sprite3d, 0);
}
Еще примеры: https://github.com/goodzsq/cocos_template/blob/77b4e669d7a6c87d2446c74e7d486106fd2055c3/doc/sample/cocos_3d.js
Планирование и обновление
// Делать обновление каждый кадр
this.scheduleUpdate( );
update: function( dt )
{
// ...
}
// Несколько раз: schedule( functionToCall, timeBetweenFunctionCalls, numberOfTimesToCallFunction, firstFunctionCallDelay );
this.schedule( this.UpdateFunction, 3.0, 5, 1.0 );
// Вечно: schedule( functionToCall, timeBetweenFunctionCalls );
this.schedule( this.UpdateFunction, 1.0 );
// Каждый кадр
this.schedule( this.UpdateFunction );
// Один раз
this.scheduleOnce( this.UpdateFunction, 1.0 );
// UNSCHEDULING
this.unschedule( this.UpdateFunction );
this.unscheduleAllCallbacks( );
this.unscheduleUpdate( );
События
Event Trigger - основа всех событий, срабатывает при любом касании.
Event Manager - вызывается при любом событии.
Event Listener - именно здесь содержится логика событий.
Touch Event - Может быть cc.EventListener.TOUCH_ONE_BY_ONE и cc.EventListener.TOUCH_ALL_AT_ONCE т.е. одно или несколько касаний.
Пример касания (движение спрайта мышью):
HelloWorldLayer = cc.Layer.extend({
ctor:function () {
this._super();
var size = cc.winSize;
this.sprite = new cc.Sprite(res.HelloWorld_png);
this.sprite.attr({ x: size.width / 2, y: size.height / 2 });
this.addChild(this.sprite, 0);
var listener = cc.EventListener.create({
event: cc.EventListener.TOUCH_ONE_BY_ONE,
swallowTouches: true, // поглотить событие и не передавать другим EventListener
onTouchBegan: function (touch, event) {
var target = event.getCurrentTarget();
var locationInNode = target.convertToNodeSpace(touch.getLocation());
var s = target.getContentSize();
var rect = cc.rect(0, 0, s.width, s.height);
if (cc.rectContainsPoint(rect, locationInNode)) {
cc.log('Touch began: Inside the sprite');
return true;
}
cc.log('Touch began: Outside the sprite');
return false;
},
onTouchMoved: function (touch, event) {
var target = event.getCurrentTarget();
target.setPosition(touch.getLocation());
},
onTouchEnded: function (touch, event) {
cc.log('Touch end');
}
});
cc.eventManager.addListener(listener, this.sprite);
return true;
},
});
var HelloWorldScene = cc.Scene.extend({
onEnter:function () {
this._super();
this.addChild( new HelloWorldLayer() );
}
});
Заметьте, что касание обрабатывается относительно сцены, хотя спрайт является целью события, поэтому нужно проверять попадание точки касания в спрайт.
Пример нажатия мышью:
HelloWorldLayer = cc.Layer.extend({
ctor:function () {
this._super();
var size = cc.winSize;
this.sprite = new cc.Sprite(res.HelloWorld_png);
this.sprite.attr({ x: size.width / 2, y: size.height / 2 });
this.addChild(this.sprite, 0);
var listener = cc.EventListener.create({
event: cc.EventListener.MOUSE,
swallowTouches: true,
ismousedown:false, // создаем флаг для проверки нажатия на спрайт
onMouseDown: function (event) {
var target = event.getCurrentTarget();
var locationInNode = target.convertToNodeSpace(event.getLocation());
var s = target.getContentSize();
var rect = cc.rect(0, 0, s.width, s.height);
if (cc.rectContainsPoint(rect, locationInNode)) {
cc.log('Mouse Down: Inside the sprite');
this.ismousedown=true;
return true;
}
cc.log('Mouse Down: Outside the sprite');
return false;
},
onMouseMove: function (event) {
if(this.ismousedown)
{
var target = event.getCurrentTarget();
target.setPosition(event.getLocation());
}
},
onMouseUp: function (event) {
cc.log('Mouse Up');
this.ismousedown=false;
}
});
//Added Event Listener To Sprite
cc.eventManager.addListener(listener, this.sprite);
return true;
},
});
var HelloWorldScene = cc.Scene.extend({
onEnter:function () {
this._super();
this.addChild( new HelloWorldLayer() );
}
});
Заметьте, что понадобилась переменная ismousedown для проверки нажатия.
Пример нажатия клавиш (движение спрайта с клавиатуры):
HelloWorldLayer = cc.Layer.extend({
ctor:function () {
this._super();
var KeyCode={
LEFT:37,
UP:38,
RIGHT:39,
DOWN:40
};
var MoveOffSet=20;
var size = cc.winSize;
this.sprite = new cc.Sprite(res.HelloWorld_png);
this.sprite.attr({ x: size.width / 2, y: size.height / 2 });
this.addChild(this.sprite, 0);
var listener = cc.EventListener.create({
event: cc.EventListener.KEYBOARD,
swallowTouches: true,
onKeyPressed: function (keyCode,event) {
var target = event.getCurrentTarget();
var position=target.getPosition();
switch(keyCode)
{
case KeyCode.LEFT:
position.x-=MoveOffSet;
break;
case KeyCode.RIGHT:
position.x+=MoveOffSet;
break;
case KeyCode.UP:
position.y+=MoveOffSet;
break;
case KeyCode.DOWN:
position.y-=MoveOffSet;
break;
}
target.setPosition(position);
},
onKeyReleased: function (event) {
cc.log('onKeyReleased');
}
});
cc.eventManager.addListener(listener, this.sprite);
return true;
},
});
var HelloWorldScene = cc.Scene.extend({
onEnter:function () {
this._super();
this.addChild( new HelloWorldLayer() );
}
});
Можно создавать собственные события, пример:
var counter = 0;
HelloWorldLayer = cc.Layer.extend({
sendEvent:function () {
counter++;
var event = new cc.EventCustom("my_custom_event");
event.setUserData(counter.toString());
cc.eventManager.dispatchEvent(event);
},
ctor:function () {
this._super();
var size = cc.winSize;
this.sprite = new cc.Sprite(res.HelloWorld_png);
this.sprite.attr({ x: size.width / 2, y: size.height / 2 });
this.addChild(this.sprite, 0);
var listener = cc.EventListener.create({
event: cc.EventListener.CUSTOM,
eventName: "my_custom_event",
callback: function(event){ cc.log("Custom event 1 received, " + event.getUserData() + " times"); }
});
cc.eventManager.addListener(listener, 1);
this.sendEvent();
this.sendEvent();
return true;
},
});
var HelloWorldScene = cc.Scene.extend({
onEnter:function () {
this._super();
this.addChild( new HelloWorldLayer() );
}
});
Функция dispatchEvent используется для срабатывания события.
Ввод (одно касание):
if ( cc.sys.capabilities.hasOwnProperty( 'touches' ) )
{
cc.eventManager.addListener(
{
event: cc.EventListener.TOUCH_ONE_BY_ONE,
// called when the touch first begins
onTouchBegan:function( touch, event )
{
cc.log( "Touch Began X: " + touch.getLocationX( ) );
cc.log( "Touch Began Y: " + touch.getLocationY( ) );
return true;
},
// called when the user moves their finger
onTouchMoved:function( touch, event )
{
cc.log( "Touch Moved X: " + touch.getLocationX( ) );
cc.log( "Touch Moved Y: " + touch.getLocationY( ) );
},
// called when the user lifts their finger
onTouchEnded:function( touch, event )
{
cc.log( "Touch Ended X: " + touch.getLocationX( ) );
cc.log( "Touch Ended Y: " + touch.getLocationX( ) );
},
// called when the device goes to another application such as a phone call
onTouchCancelled:function( touch, event )
{
cc.log( "Touch Cancelled X: " + touch.getLocationX( ) );
cc.log( "Touch Cancelled Y: " + touch.getLocationX( ) );
}
}, this );
}
Ввод нескольких касаний:
// checks if the device you are using is capable of touch
if ( cc.sys.capabilities.hasOwnProperty( 'touches' ) )
{
cc.eventManager.addListener(
{
event: cc.EventListener.TOUCH_ALL_AT_ONCE,
onTouchesBegan:function( touches, event )
{
cc.log( "Touch Began: " + touches[touchIndex].getLocationX( ) );
},
onTouchesMoved:function( touches, event )
{
cc.log( "Touch Moved: " + touches[touchIndex].getLocationX( ) );
},
onTouchesEnded:function( touches, event )
{
cc.log( "Touch Ended: " + touches[touchIndex].getLocationX( ) );
},
onTouchesCancelled:function( touches, event )
{
cc.log( "Touch Cancelled: " + touches[touchIndex].getLocationX( ) );
}
}, this);
}
Ввод мышью:
if ( cc.sys.capabilities.hasOwnProperty( 'mouse' ) )
{
cc.eventManager.addListener(
{
event: cc.EventListener.MOUSE,
onMouseDown: function( event )
{
if ( event.getButton( ) == cc.EventMouse.BUTTON_LEFT )
{
cc.log( "Left mouse button pressed at " + event.getLocationX( ) );
}
},
onMouseUp: function(event)
{
if ( event.getButton() == cc.EventMouse.BUTTON_LEFT )
{
cc.log( "Left mouse button released at " + event.getLocationX( ) );
}
},
onMouseMove: function(event)
{
cc.log( "Mouse Moved: " + event.getLocationX( ) );
},
onMouseScroll: function(event)
{
cc.log( "Scroll: " + event.getLocationX( ) );
}
}, this );
}
Ввод с клавиатуры:
if ( cc.sys.capabilities.hasOwnProperty( 'keyboard' ) )
{
cc.eventManager.addListener(
{
event: cc.EventListener.KEYBOARD,
onKeyPressed: function( key, event )
{
cc.log( "Key Pressed: " + key.toString( ) );
},
onKeyReleased: function( key, event )
{
cc.log( "Key Released: " + key.toString( ) );
}
}, this );
}
Акселерометр - ввод наклоном:
if ( cc.sys.capabilities.hasOwnProperty( 'accelerometer' ) )
{
cc.inputManager.setAccelerometerInterval( 1 / 60 );
cc.inputManager.setAccelerometerEnabled( true );
cc.eventManager.addListener(
{
event: cc.EventListener.ACCELERATION,
callback: function( accelEvent, event )
{
cc.log( 'Accel X: ' + accelEvent.x + ' Y: ' + accelEvent.y + ' Z: ' + accelEv
}
}, this );
}
virtual pads
Ghost buttons
virtual analogic pad
Примеры в игре: https://github.com/noelyahan/cocos2djstut/blob/415694ebd4e58afd05855d45473d71ee9bb9a3ed/game-five/src/gamescript.js
Разделение экрана:
var touchListener = cc.EventListener.create({
event: cc.EventListener.TOUCH_ONE_BY_ONE,
swallowTouches: true,
onTouchBegan: function (touch, event) {
if(touch.getLocation().x < 240){
xSpeed = -2;
left.setOpacity(255);
right.setOpacity(128);
}
else{
xSpeed = 2;
right.setOpacity(255);
left.setOpacity(128);
}
return true;
},
onTouchEnded:function (touch, event) {
xSpeed = 0;
left.setOpacity(128);
right.setOpacity(128);
}
})
Виртуальные кнопки из двух окружностей:
var itemsLayer;
var cart;
var xSpeed = 0;
var touchOrigin;
var touching = false;
var touchEnd;
// ...
var touchListener = cc.EventListener.create({
event: cc.EventListener.TOUCH_ONE_BY_ONE,
swallowTouches: true,
onTouchBegan: function (touch, event) {
touchOrigin = cc.Sprite.create("assets/touchorigin.png");
topLayer.addChild(touchOrigin,0);
touchOrigin.setPosition(touch.getLocation().x,touch.
getLocation().y);
touchEnd = cc.Sprite.create("assets/touchend.png");
topLayer.addChild(touchEnd,0);
touchEnd.setPosition(touch.getLocation().x,touch.getLocation().y);
touching = true;
return true;
},
onTouchMoved: function (touch, event) {
touchEnd.setPosition(touch.getLocation().x,touchEnd.
getPosition().y);
},
onTouchEnded:function (touch, event) {
touching = false;
topLayer.removeChild(touchOrigin);
topLayer.removeChild(touchEnd);
}
})
Управление без вывода спрайтов:
detectedX and savedX to store the initial and current touch horizontal coordinate.
var itemsLayer;
var xSpeed = 0;
var cart;
var detectedX;
var savedX;
var touching=false;
// ...
var touchListener = cc.EventListener.create({
event: cc.EventListener.TOUCH_ONE_BY_ONE,
swallowTouches: true,
onTouchBegan: function (touch, event) {
touching = true;
detectedX = touch.getLocation().x;
savedX = detectedX
return true;
},
onTouchMoved: function (touch, event) {
detectedX = touch.getLocation().x;
},
onTouchEnded:function (touch, event) {
touching = false;
}
})
Эффекты
Создать эффект частиц можно используя https://71squared.com/particledesigner
Пример:
var emitter = cc.ParticleSun.create();
this.addChild(emitter,1);
var myTexture = cc.textureCache. addImage("assets/particle.png");
emitter.setTexture(myTexture);
emitter.setStartSize(2);
emitter.setEndSize(4);
Звук
Лучшие форматы для разных систем: MP3, WAV, and OGG.
Редактор музыки - http://audacity.sourceforge.net/
Пример меню со звуками: https://github.com/noelyahan/cocos2djstut/blob/415694ebd4e58afd05855d45473d71ee9bb9a3ed/game-four/src/gamescript.js
Сайты с бесплатной музыкой: http://v-play.net/2015/06/16-sites-featuring-free-game-sounds/
Предзагрузка (в cc.game.onStart):
cc.audioEngine.preloadMusic(s_music_background);
cc.audioEngine.preloadEffect(s_music_jump);
Проигрывание: эффект представляет короткий звук
cc.audioEngine.playEffect( "audioFilePath" );
cc.audioEngine.playEffect( "audioFilePath", true ); // повторение
cc.audioEngine.stopEffect->stopEffect( soundEffectID );
cc.audioEngine.pauseEffect( soundEffectID );
cc.audioEngine.resumeEffect( soundEffectID );
cc.audioEngine.stopAllEffect( soundEffectID );
cc.audioEngine.pauseAllEffect( soundEffectID );
cc.audioEngine.resumeAllEffect( soundEffectID );
cc.audioEngine.setEffectsVolume( 0.25 );
Музыка: Представляет долгий фоновый звук
cc.audioEngine.playMusic( "audioFilePath", true );
cc.audioEngine.stopMusic( );
cc.audioEngine.pauseMusic( );
cc.audioEngine.resumeMusic( );
cc.audioEngine.setMusicVolume( ( 0.25 );
The getMusicVolume and setMusicVolume methods get and set music volume with values from 0 (no volume) to 1 (full volume), respectively
Файлы
Домументация:
Из класса cc.sys можно узнать свойства системы, например, язык cc.sys.language - https://github.com/cocos2d/cocos2d-x/blob/4ab1540ccc27185bb468d361fcfe6bd4d284b4f5/tests/js-tests/src/CurrentLanguageTest/CurrentLanguageTest.js
Локальное хранилище:
cc.sys.localStorage.getItem(key);
cc.sys.localStorage.setItem(key, value);
cc.sys.localStorage.removeItem(key);
Видео: https://www.youtube.com/watch?v=2A5dVQv1C8o
На нативных платформах jsb.fileUtils:
jsb.fileUtils.writeToFile({data:JSON.stringify(file)}, path+levelDataFileName)
Чтение и запись PLIST:
http://cocos.sonarlearning.co.uk/docs/plist
Физика
Простейшие столкновения (Rect/Bounding Box):
var rect1 = node1.getBoundingBox( );
var rect2 = node2.getBoundingBox( );
if ( cc.rectIntersectsRect( rect1, rect2 ) )
{
cc.log( "Collided" );
}
else
{
cc.log( "Not collided" );
}
Проверка касания:
onTouchBegan:function( touch, event )
{
var rect1 = node1.getBoundingBox( );
var touchPoint = touch.getLocation( );
if ( cc.rectContainsPoint( rect1, touchPoint ) )
{
cc.log( "Touched" );
}
else
{
cc.log( "Not touched" );
}
return true;
},
Пример игры на Box2d и cocos: в разделе примеров
Уроки по Box2d:
http://www.iforce2d.net/b2dtut/
http://creativejs.com/2011/09/box2d-javascript-tutorial-series-by-seth-ladd/
https://www.codeandweb.com/blog/2015/07/15/cocos2d-physics-tutorial
В cocos2d популярна физическая библиотека Chipmung. Спрайты cocos2d-js сопоставляются физическим телам, а Chipmung сам обновит их свойства.
Уроки Chipmung: http://blog.galantegames.com/tag/cocos2d-x/
staticBody - не подвержен физике.
PhysicsSprite - подвержены физике.
Не забудьте подключить в project.json:
"modules"
:
[
"cocos2d"
,....,
"chipmunk"
]
Примерchipmunk
кода:
var g_groundHeight = 57;
var g_runnerStartX = 80;
var WALLS_WIDTH = 5;
var WALLS_ELASTICITY = 1;
var WALLS_FRICTION = 1;
HelloWorldLayer = cc.Layer.extend({
sprite:null,
ctor:function () {
this._super();
this.initPhysics();
this.setupDebugNode();
this.addWallsAndGround();
this.addPhysicsCircle();
this.addPhysicsBox();
this.addCollisionCallBack();
this.scheduleUpdate();
return true;
},
initPhysics:function() {
this.space = new cp.Space();
this.space.gravity = cp.v(0, -800); // Гравитация
this.space.iterations = 30;
this.space.sleepTimeThreshold = Infinity;
this.space.collisionSlop = Infinity;
},
addCollisionCallBack:function(){
this.space.addCollisionHandler(0, 1, function(){
cc.log('Box and Circle collaiding !');
return true;
}, null, null, null);
},
update:function (dt) {
this.space.step(dt);
},
addWallsAndGround: function() {
var leftWall = new cp.SegmentShape(this.space.staticBody, new cp.v(0, 0), new cp.v(0, 1000000), WALLS_WIDTH);
leftWall.setElasticity(WALLS_ELASTICITY);
leftWall.setFriction(WALLS_FRICTION);
this.space.addStaticShape(leftWall);
var rightWall = new cp.SegmentShape(this.space.staticBody, new cp.v(cc.winSize.width, 1000000), new cp.v(cc.winSize.width, 0), WALLS_WIDTH);
rightWall.setElasticity(WALLS_ELASTICITY);
rightWall.setFriction(WALLS_FRICTION);
this.space.addStaticShape(rightWall);
var bottomWall = new cp.SegmentShape(this.space.staticBody, new cp.v(0, 0), new cp.v(cc.winSize.width, 0), WALLS_WIDTH);
bottomWall.setElasticity(WALLS_ELASTICITY);
bottomWall.setFriction(WALLS_FRICTION);
this.space.addStaticShape(bottomWall);
var upperWall = new cp.SegmentShape(this.space.staticBody, new cp.v(0, cc.winSize.height), new cp.v(cc.winSize.width, cc.winSize.height), WALLS_WIDTH);
upperWall.setElasticity(WALLS_ELASTICITY);
upperWall.setFriction(WALLS_FRICTION);
this.space.addStaticShape(upperWall);
},
setupDebugNode : function()
{
this._debugNode = new cc.PhysicsDebugNode(this.space);
this.addChild( this._debugNode );
},
addPhysicsCircle: function() {
var width=50,height=50,mass=1;
this.phBodyCircle = this.space.addBody(new cp.Body(mass, cp.momentForCircle(mass,0,width*0.5,cc.p(0,0))));
this.phBodyCircle.setPos(cc.p(cc.winSize.width * 0.5, cc.winSize.height * 0.3));
var phShape = this.space.addShape(new cp.CircleShape(this.phBodyCircle, width, cc.p(0, 0)));
phShape.setFriction(0);
phShape.setElasticity(1);
phShape.setCollisionType(0);
},
addPhysicsBox: function() {
var width=50,height=50,mass=1;
this.phBodyBox = this.space.addBody(new cp.Body(mass, cp.momentForBox(mass, width,height)));
this.phBodyBox.setPos(cc.p(cc.winSize.width * 0.5, cc.winSize.height * 0.1));
var phShape = this.space.addShape(new cp.BoxShape(this.phBodyBox, width, height));
phShape.setFriction(0);
phShape.setElasticity(1);
phShape.setCollisionType(1);
}
});
var HelloWorldScene = cc.Scene.extend({
onEnter:function () {
this._super();
this.addChild( new HelloWorldLayer() );
}
});
В результате на сцене будут прыгающие круг и квадрат ограниченные стенами.
cp.Space - основное физическое пространство, ему задается гравитация и т.д.
Физика обновляется с помощью this
.
space
.
step
(
dt
)
Тела cp.Body добавляются this
.
space
.
addBody. Мы добавили cp.CircleShape и cp.BoxShape
Для создания стен т.е. границ сцены используется cp.SegmentShape которые добавляются кthis
.
space
.
staticBody
Поскольку для фигур не задан PhysicSprite, то чтобы их увидеть надо использовать отладку
cc
.
PhysicsDebugNode.
setCollisionType определяет будет ли проверка столкновений для тела.
addCollisionHandler с тегами 0, 1 для квадрата и круга используется для проверки столкновения.
Скрипты
http://www.cocos2d-x.org/docs/programmers-guide/10/index.html
Шейдеры
Графику можно обрабатывать с помощью шейдеров.
vertex shader - обрабатые данные вершин
fragment shader - обрабатывает цвет пикселей
Используются GL шейдеры. Класс cc.GLProgram - представляет шейдер элемента.
setShaderProgram и getShaderProgram позволяют задать/получить GLProgram.
Рекомендуется к изучению OpenGL ES Shading Language v1.0 Spec
Примеры того что можно сделать с помощью шейдеров: https://www.shadertoy.com/
Примеры шейдеров в cocos: http://acherkashin.me/2015/07/10/cocos2d-xshaders/
Space glsl cocos2d shader (процедурная генерация космоса): https://gist.github.com/derofim/22c63b41305cd3684d47
Урок: [Cocos3.8 Tutorial] RenderTexture + Blur http://discuss.cocos2d-x.org/t/cocos3-8-tutorial-rendertexture-blur/13622
Простой вершинный шейдер:
attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec4 a_color;
#ifdef GL_ES
varying mediump vec2 v_texCoord;
varying mediump vec4 v_fragmentColor;
#else
varying vec2 v_texCoord;
varying vec4 v_fragmentColor;
#endif
void main()
{
gl_Position = (CC_PMatrix * CC_MVMatrix) * a_position; // on web
// gl_Position = CC_PMatrix * a_position; // on native and mobile
v_fragmentColor = a_color;
v_texCoord = a_texCoord;
}
Простой фрагментный шейдер:
#ifdef GL_ES
precision lowp float;
#endif
varying vec2 v_texCoord;
void main() {
vec4 normalColor = texture2D(CC_Texture0, v_texCoord).rgba;
gl_FragColor = normalColor;
}
Создание шейдера:
this.shader = new cc.GLProgram(res.gray_vsh, res.gray_fsh);
this.shader.addAttribute(cc.ATTRIBUTE_NAME_POSITION, cc.VERTEX_ATTRIB_POSITION); // a_position
this.shader.addAttribute(cc.ATTRIBUTE_NAME_TEX_COORD, cc.VERTEX_ATTRIB_TEX_COORDS); // a_texCoord
this.shader.addAttribute(cc.ATTRIBUTE_NAME_COLOR, cc.VERTEX_ATTRIB_COLOR); // a_color
this.shader.link(); // links the glProgram
this.shader.updateUniforms(); // updateUniforms will create PMATRIX, MVMATRIX, MVPMATRIX, SAMPLER
this.shader.use();
this.sprite.setShaderProgram(this.shader);
Код может немного изменяться в зависимости от платформы, поэтому рекомендуется использовать cc.sys.isNative.
Уроки по созданию шейдеров https://github.com/mattdesl/lwjgl-basics/wiki/ShaderLesson3
Работа с писельным шейдером (.fsh):
Заливка цветом:
gl_FragColor = vec4(1,0.0,0.0,1.0);
Градиент:
vec2 xy = v_texCoord.xy;
gl_FragColor = vec4(xy.x,0.0,0.0,1.0);
Градиент поверх текстуры:
vec4 normalColor = texture2D(CC_Texture0, v_texCoord).rgba;
normalColor.b = v_texCoord.x;
gl_FragColor = normalColor;
Передача праметра в шейдер:
var param = 0.6;
if (cc.sys.isNative) {
var glProgram_state = cc.GLProgramState.getOrCreateWithGLProgram(this.shader);
glProgram_state.setUniformFloat("u_param", param);
this.sprite.setGLProgramState(glProgram_state);
} else {
this.shader.setUniformLocationWith1f(this.shader.getUniformLocationForName('u_param'), param);
this.sprite.setShaderProgram(this.shader);
}
Пример: вектор глобального освещения
var light = new cc.math.Vec3(0.2, 0.2, 0.2);
if (cc.sys.isNative) {
var glProgram_state = cc.GLProgramState.getOrCreateWithGLProgram(this.shader);
glProgram_state.setUniformVec3("vLighting", light);
this.sprite.setGLProgramState(glProgram_state);
} else {
this.shader.setUniformLocationWith3f(this.shader.getUniformLocationForName('vLighting'), light.x, light.y, light.z);
this.sprite.setShaderProgram(this.shader);
}
Шейдер .fsh:
#ifdef GL_ES
precision lowp float;
#endif
varying vec2 v_texCoord;
uniform vec3 vLighting;
void main() {
vec4 normalColor = texture2D(CC_Texture0, v_texCoord).rgba;
gl_FragColor = vec4(normalColor.rgb * vLighting, normalColor.a);
}
Пример анимации шума:
var shaderScene;
HelloWorldLayer = cc.Layer.extend({
ctor:function () {
this._super();
var size = cc.winSize;
this.addChild(new cc.LayerColor(cc.color(200, 200 ,200, 255)));
this.sprite = new cc.Sprite(res.HelloWorld_png);
this.sprite.attr({ x: size.width / 2, y: size.height / 2 });
if (cc.sys.isNative) {
shaderScene = new cc.GLProgram(res.gray_vsh, res.gray_fsh);
} else {
shaderScene = new cc.GLProgram(res.grayweb_vsh, res.gray_fsh);
}
shaderScene.addAttribute(cc.ATTRIBUTE_NAME_POSITION, cc.VERTEX_ATTRIB_POSITION); // a_position
shaderScene.addAttribute(cc.ATTRIBUTE_NAME_TEX_COORD, cc.VERTEX_ATTRIB_TEX_COORDS); // a_texCoord
shaderScene.addAttribute(cc.ATTRIBUTE_NAME_COLOR, cc.VERTEX_ATTRIB_COLOR); // a_color
shaderScene.link(); // links the glProgram
shaderScene.updateUniforms();
shaderScene.setUniformsForBuiltins();
shaderScene.use();
var light = new cc.math.Vec3(0.2, 0.2, 0.2);
if (cc.sys.isNative) {
var glProgram_state = cc.GLProgramState.getOrCreateWithGLProgram(shaderScene);
glProgram_state.setUniformVec3("vLighting", light);
this.sprite.setGLProgramState(glProgram_state);
} else {
shaderScene.setUniformLocationWith3f(shaderScene.getUniformLocationForName('vLighting'), light.x, light.y, light.z);
this.sprite.setShaderProgram(shaderScene);
}
this.addChild(this.sprite, 0);
this.scheduleUpdate();
return true;
},
update: function( dt )
{
shaderScene.updateUniforms();
shaderScene.setUniformsForBuiltins();
shaderScene.use();
}
});
var HelloWorldScene = cc.Scene.extend({
onEnter:function () {
this._super();
this.addChild( new HelloWorldLayer() );
}
});
fsh:
#ifdef GL_ES
precision mediump float;
#endif
//uniform vec3 vLighting;
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
float randomNoise(vec2 p, float t) {
return fract(6791.*sin(47.*p.x+p.y*9973.) + t);
}
void main() {
vec2 pos = v_texCoord;
float n = randomNoise(pos, CC_Time.x);
gl_FragColor = vec4(vec3(n), 1.);
}
vsh:
#ifdef GL_ES
precision mediump float;
#endif
attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec4 a_color;
#ifdef GL_ES
varying mediump vec2 v_texCoord;
varying mediump vec4 v_fragmentColor;
#else
varying vec2 v_texCoord;
varying vec4 v_fragmentColor;
#endif
void main()
{
gl_Position = (CC_PMatrix * CC_MVMatrix) * a_position; // on web
// gl_Position = CC_PMatrix * a_position; // on native and mobile
v_fragmentColor = a_color;
v_texCoord = a_texCoord;
}
Пример рисования окружности с контуром:
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
//uniform vec3 vLighting;
void main() {
float center = 0.5;
float radius = 0.5;
vec2 position = v_texCoord.xy - center;
float thickness = radius/2.; // толщина контура, при thickness = radius будет заполненный овал
float z = sqrt(radius*radius - position.x*position.x - position.y*position.y);
vec3 normal = normalize(vec3(position.x, position.y, z));
if (length(position) > radius || (length(position) < radius-thickness)) {
gl_FragColor = vec4(vec3(0.5), 1.);
} else {
gl_FragColor = vec4((normal+1.)/2., 1.);
}
//gl_FragColor = texture2D(CC_Texture0,v_texCoord).bgra;
}
Пример обрезки текстуры по окружности:
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
//uniform vec3 vLighting;
void main() {
float center = 0.5;
float radius = 0.5;
vec2 position = v_texCoord.xy - center;
float thickness = radius/1.2; // толщина контура, при thickness = radius будет заполненный овал
const vec3 cLight = normalize(vec3(.5, .5, 1.));
float z = sqrt(radius*radius - position.x*position.x - position.y*position.y);
vec3 normal = normalize(vec3(position.x, position.y, z));
if (length(position) > radius || (length(position) < radius-thickness)) {
gl_FragColor = vec4(vec3(0.5), 1.);
} else {
float diffuse = max(0., dot(normal, cLight));
gl_FragColor = vec4(vec3(diffuse), 1.)*texture2D(CC_Texture0,v_texCoord);
}
}
Пример простого затемнения / освещения спрайта:
#ifdef GL_ES
precision mediump float;
#endif
//uniform vec3 vLighting;
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
void main() {
const vec3 lightPos = vec3(0., 0., 1.);
float lightRadius = 0.5;
float lightCenter = 0.5;
vec2 position = v_texCoord.xy - lightCenter;
const vec3 cLight = normalize(lightPos);
float z = sqrt(lightRadius*lightRadius - position.x*position.x - position.y*position.y);
vec3 normal = normalize(vec3(position.x, position.y, z));
float diffuse = max(0., dot(normal, cLight));
gl_FragColor = vec4(vec3(diffuse), 1.)*texture2D(CC_Texture0,v_texCoord);
}
Подобным образом можно создать динамическое освещение https://www.codeandweb.com/blog/2015/05/12/lighting-demo-cocos2d-x
Сглаженный увеличенный шум (подробнее на http://www.raywenderlich.com/70208/opengl-es-pixel-shaders-tutorial ):
#ifdef GL_ES
precision mediump float;
#endif
//uniform vec3 vLighting;
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
float randomNoise(vec2 p) {
return fract(6791.*sin(47.*p.x+p.y*9973.));
}
float smoothNoise(vec2 p) {
vec2 nn = vec2(p.x, p.y+1.);
vec2 ee = vec2(p.x+1., p.y);
vec2 ss = vec2(p.x, p.y-1.);
vec2 ww = vec2(p.x-1., p.y);
vec2 cc = vec2(p.x, p.y);
float sum = 0.;
sum += randomNoise(nn)/8.;
sum += randomNoise(ee)/8.;
sum += randomNoise(ss)/8.;
sum += randomNoise(ww)/8.;
sum += randomNoise(cc)/2.;
return sum;
}
void main() {
float scaleResolution = 20.;
vec2 pos = v_texCoord.xy/scaleResolution;
//vec2 position = gl_FragCoord.xy/uResolution.xx;
float tiles = 128.;
vec2 position = floor(pos*tiles);
float n = smoothNoise(position);
gl_FragColor = vec4(vec3(n), 1.);
}
Размытие изображения:
vsh:
#ifdef GL_ES
precision mediump float;
#endif
attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec4 a_color;
#ifdef GL_ES
varying mediump vec2 v_texCoord;
varying mediump vec4 v_fragmentColor;
#else
varying vec2 v_texCoord;
varying vec4 v_fragmentColor;
#endif
varying vec2 v_blurTexCoords[14];
//uniform float u_rate;
void main()
{
float u_rate = 1.8;
gl_Position = (CC_PMatrix * CC_MVMatrix) * a_position; // on web
// gl_Position = CC_PMatrix * a_position; // on native and mobile
v_fragmentColor = a_color;
v_texCoord = a_texCoord;
v_blurTexCoords[ 0] = v_texCoord + vec2(-0.028 * u_rate, 0.0);
v_blurTexCoords[ 1] = v_texCoord + vec2(-0.024 * u_rate, 0.0);
v_blurTexCoords[ 2] = v_texCoord + vec2(-0.020 * u_rate, 0.0);
v_blurTexCoords[ 3] = v_texCoord + vec2(-0.016 * u_rate, 0.0);
v_blurTexCoords[ 4] = v_texCoord + vec2(-0.012 * u_rate, 0.0);
v_blurTexCoords[ 5] = v_texCoord + vec2(-0.008 * u_rate, 0.0);
v_blurTexCoords[ 6] = v_texCoord + vec2(-0.004 * u_rate, 0.0);
v_blurTexCoords[ 7] = v_texCoord + vec2( 0.004 * u_rate, 0.0);
v_blurTexCoords[ 8] = v_texCoord + vec2( 0.008 * u_rate, 0.0);
v_blurTexCoords[ 9] = v_texCoord + vec2( 0.012 * u_rate, 0.0);
v_blurTexCoords[10] = v_texCoord + vec2( 0.016 * u_rate, 0.0);
v_blurTexCoords[11] = v_texCoord + vec2( 0.020 * u_rate, 0.0);
v_blurTexCoords[12] = v_texCoord + vec2( 0.024 * u_rate, 0.0);
v_blurTexCoords[13] = v_texCoord + vec2( 0.028 * u_rate, 0.0);
}
fsh:
#ifdef GL_ES
precision mediump float;
#endif
//uniform vec3 vLighting;
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
varying vec2 v_blurTexCoords[14];
uniform vec2 u_resolution;
void main() {
vec2 resolution = u_resolution;
gl_FragColor = vec4(0.0);
gl_FragColor += texture2D(CC_Texture0, v_blurTexCoords[ 0])*0.0044299121055113265;
gl_FragColor += texture2D(CC_Texture0, v_blurTexCoords[ 1])*0.00895781211794;
gl_FragColor += texture2D(CC_Texture0, v_blurTexCoords[ 2])*0.0215963866053;
gl_FragColor += texture2D(CC_Texture0, v_blurTexCoords[ 3])*0.0443683338718;
gl_FragColor += texture2D(CC_Texture0, v_blurTexCoords[ 4])*0.0776744219933;
gl_FragColor += texture2D(CC_Texture0, v_blurTexCoords[ 5])*0.115876621105;
gl_FragColor += texture2D(CC_Texture0, v_blurTexCoords[ 6])*0.147308056121;
gl_FragColor += texture2D(CC_Texture0, v_texCoord )*0.159576912161;
gl_FragColor += texture2D(CC_Texture0, v_blurTexCoords[ 7])*0.147308056121;
gl_FragColor += texture2D(CC_Texture0, v_blurTexCoords[ 8])*0.115876621105;
gl_FragColor += texture2D(CC_Texture0, v_blurTexCoords[ 9])*0.0776744219933;
gl_FragColor += texture2D(CC_Texture0, v_blurTexCoords[10])*0.0443683338718;
gl_FragColor += texture2D(CC_Texture0, v_blurTexCoords[11])*0.0215963866053;
gl_FragColor += texture2D(CC_Texture0, v_blurTexCoords[12])*0.00895781211794;
gl_FragColor += texture2D(CC_Texture0, v_blurTexCoords[13])*0.0044299121055113265;
}
Cocos javascript shader cross platform from file and passing parameter: https://gist.github.com/derofim/ff68fb252da8f70b33f9
cocos2d-x position issue when using setShaderProgram: http://stackoverflow.com/questions/34369687/cocos2d-x-position-issue-when-using-setshaderprogram
Мой пример загрузки простого шейдера из файлов: https://gist.github.com/derofim/69a4077c09708b7f562f
Мой пример шейдера используя initWithVertexShaderByteArray: https://gist.github.com/derofim/39ceb8a67d24284f32a9
Bare-Bones Shader Example in Cocos2d-x v.3.2: http://www.roguish.com/blog/?p=746
Официальные тесты: https://github.com/cocos2d/cocos2d-x/blob/0abe235323aaf03f457d5fa395ded1cd38c43713/tests/js-tests/src/OpenGLTest/OpenGLTest.js
Разделение кода http://discuss.cocos2d-x.org/t/shader-is-causing-transparent-part-of-png-files-to-render-black/22902
При работе с шейдерами может потребоваться настроить сглаживание.
Вдохновленный статьей http://www.gamedevcraft.com/2015/10/disable-texture-anti-aliasing.html я нашел функцию setTexParameters:
// GL_NEAREST, GL_NEAREST, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE
var texParams = {minFilter: gl.NEAREST, magFilter: gl.NEAREST, wrapS: gl.CLAMP_TO_EDGE, wrapT: gl.CLAMP_TO_EDGE};
this.sprite.getTexture().setTexParameters(texParams);
Эта функция задает texParameteri:
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texParams.minFilter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texParams.magFilter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, texParams.wrapS);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, texParams.wrapT);
Этого же можно добиться с помощью setAliasTexParameters:
var sprite2 = cc.Sprite.create(res.sprites_png);
sprite2.attr({ x: size.width / 2, y: size.height / 2 });
var texture = sprite2.getTexture();
texture.setAliasTexParameters(); // включить Alias
texture.setAntiAliasTexParameters(); // снова выключить Alias
sprite2.scale = 10;
this.addChild(sprite2, 0);
Еще примеры шейдеров:
Network
Socket.io и cocos2d: https://gist.github.com/derofim/1cd407d8ec414d0007df
Multiplayer Card Game using WebSockets,Java Netty Server ,Cocos2d-x-HTML5 http://www.gamedevcraft.com/2016/01/multiplayer-card-game-using.html
pomelo-cocos2d-js client https://github.com/NetEase/pomelo-cocos2d-js
Бесплатные хостинги на время разработки:
heroku
AWS
c9.io
Примеры игр
Плитки
Игра на угадывание (найти одинаковые плитки).
Демо: http://www.ipastimes.com/game/LearningCocos/
Исходный код: http://www.ipastimes.com/post/48.html
Картинки: https://gist.github.com/derofim/94ed75aeceaa1eb0f333#gistcomment-1709854
var gameLayer;
var gameArray = [0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7];
var pickedTiles = [];
var scoreText;
var moves=0;
var gameScene = cc.Scene.extend({
onEnter:function () {
this._super();
gameArray = shuffle(gameArray); //打乱数组排序
gameLayer = new game();
gameLayer.init();
this.addChild(gameLayer);
}
});
var game = cc.Layer.extend({
init: function () {
this._super();
var backgroundLayer = new cc.LayerColor(new cc.Color(40,40,40,255),320, 480);
this.addChild(backgroundLayer);
var gradient = new cc.LayerGradient(cc.color(0,0,0,255),cc.color(0x46,0x82,0xB4,255));
this.addChild(gradient);
scoreText = cc.LabelTTF.create("Moves: 0","Arial","32",cc.TEXT_ALIGNMENT_CENTER);
this.addChild(scoreText);
scoreText.setPosition(90,50);
for(i=0;i<16;i++){
var tile = new MemoryTile();
tile.pictureValue = gameArray[i];
this.addChild(tile,0);
tile.setPosition(49+i%4*74,400-Math.floor(i/4)*74);
}
}
});
В игре класс sprite используется для создания нового класса MemoryTile:
var MemoryTile = cc.Sprite.extend({
ctor:function() {
this._super();
this.initWithFile("assets/cover.png");
var listener = cc.EventListener.create({
event: cc.EventListener.TOUCH_ONE_BY_ONE,
swallowTouches: true,
onTouchBegan: function (touch, event) {
if(pickedTiles.length<2) {
var target = event.getCurrentTarget();
var location = target.convertToNodeSpace(touch.getLocation());
var targetSize = target.getContentSize();
var targetRectangle = cc.rect(0, 0, targetSize.width, targetSize.height);
if (cc.rectContainsPoint(targetRectangle, location)) {
if(pickedTiles.indexOf(target)==-1) {
target.initWithFile("assets/tile_" + target.pictureValue + ".png");
pickedTiles.push(target);
if(pickedTiles.length==2){
checkTiles();
}
}
}
}
}
});
cc.eventManager.addListener(listener.clone(), this);
}
});
Заметьте clone() в cc.eventManager.addListener(listener.clone(), this); использующийся для создания нового события.
Массив pickedTiles позволяет избежать выбора одного и того же тайла. В pickedTiles хранятся event.getCurrentTarget() - текущие нажатые элементы.Когда в pickedTiles содержатся два тайла вызывается проверка checkTiles:
function checkTiles(){
moves++;
scoreText.setString("Moves: "+moves);
var pause = setTimeout(function(){
if(pickedTiles[0].pictureValue!=pickedTiles[1].pictureValue){
pickedTiles[0].initWithFile("assets/cover.png");
pickedTiles[1].initWithFile("assets/cover.png");
}else{
gameLayer.removeChild(pickedTiles[0]);
gameLayer.removeChild(pickedTiles[1]);
}
pickedTiles = [];
},500);
}
checkTiles показывает тайлы две секунды и скрывает несовпавшие или удаляет совпавшие.
Фкнкция shuffle перемешивает элементы массива, она использует алгоритм http://jsfromhell.com/array/shuffle
shuffle = function(v){
for(var j, x, i = v.length; i; j = parseInt(Math.random() * i), x = v[--i], v[i] = v[j], v[j] = x);
return v;
};
Пример игры с бесконечной прокруткой:
Исходный код: https://github.com/noelyahan/cocos2djstut/tree/415694ebd4e58afd05855d45473d71ee9bb9a3ed/game-two
Мои изменения чтобы спрайты были в полный экран (todo): https://gist.github.com/derofim/34da0d09bf696630a027
Демо: играть
var res = {
background : "assets/background.png",
ship : "assets/ship.png",
particle : "assets/particle.png",
asteroid : "assets/asteroid.png"
};
var gameResources = [];
for (var i in res) {
gameResources.push(res[i]);
}
Для игры размером 480 x 320 фон должен быть 480*2=960 для создания эффекта плавной прокрутки. Код для прокрутки спрайта:
var ScrollingBG = cc.Sprite.extend({
ctor:function() {
this._super();
this.initWithFile("assets/background.png");
},
onEnter:function() {
this.setPosition(480,160);
},
scroll:function(){
this.setPosition(this.getPosition().
x-scrollSpeed,this.getPosition().y);
if(this.getPosition().x<0){
this.setPosition(this.getPosition().x+480,this.getPosition().y);
}
}
});
Код астероида:
var Asteroid = cc.Sprite.extend({
ctor:function() {
this._super();
this.initWithFile("assets/asteroid.png");
},
onEnter:function() {
this._super();
this.setPosition(600,Math.random()*320);
var moveAction= cc.MoveTo.create(2.5, new cc.Point(-100,Math.
random()*320));
this.runAction(moveAction);
this.scheduleUpdate();
},
update:function(dt){
if(this.getPosition().x<-50){
gameLayer.removeAsteroid(this)
}
}
});
Пример игры Sokoban - передвижение блоков - tile-based game
Исходный код и файлы: https://github.com/noelyahan/cocos2djstut/tree/415694ebd4e58afd05855d45473d71ee9bb9a3ed/game-three
Игровые файлы в plist:
var gameResources = [ "assets/spritesheet.plist", "assets/spritesheet.png" ];
Уровень:
var level = [
[1,1,1,1,1,1,1],
[1,1,0,0,0,0,1],
[1,1,3,0,2,0,1],
[1,0,0,4,0,0,1],
[1,0,3,1,2,0,1],
[1,0,0,1,1,1,1],
[1,1,1,1,1,1,1]
];
Each item represents a tile, and each value represents an item, which I coded this way:
• 0: This item is an empty tile
• 1: This item is a wall
• 2: This item is the place where to drop a crate
• 3: This item is the crate
• 4: This item is the player
• 5: This item is the crate on a place where to drop a crate (3+2)
• 6: This item is the player on a place where to drop a crate (4+2)
Загрузка статического изображения и настройка сглаживания:
var game = cc.Layer.extend({
init:function () {
this._super();
cache = cc.spriteFrameCache;
cache.addSpriteFrames("assets/spritesheet.plist", "assets/
spritesheet.png");
var backgroundSprite = cc.Sprite.create(cache.
getSpriteFrame("background.png"));
backgroundSprite.getTexture().setAliasTexParameters();
backgroundSprite.setPosition(240,160);
backgroundSprite.setScale(5);
this.addChild(backgroundSprite);
var levelSprite = cc.Sprite.create(cache.getSpriteFrame("level.
png"));
levelSprite.setPosition(240,110);
levelSprite.setScale(5);
this.addChild(levelSprite);
}
});
setAliasTexParameters задает за один раз параметры сглаживания сразу всем спрайтам.
• cratesArray: This is the array that will contain all crate sprites
• playerPosition: This is the variable that will be used to store a player's position inside the maze
• playerSprite: This variable represents the player itself
var game = cc.Layer.extend({
init:function () {
this._super();
// same as before
this.addChild(levelSprite);
for(i=0;i<7;i++){
cratesArray[i]=[];
for(j=0;j<7;j++){
switch(level[i][j]){
case 4:
case 6:
playerSprite = cc.Sprite.create(cache.
getSpriteFrame("player.png"));
playerSprite.setPosition(165+25*j,185-25*i);
playerSprite.setScale(5);
this.addChild(playerSprite);
playerPosition = {x:j,y:i};
cratesArray[i][j]=null;
break;
case 3:
case 5:
var crateSprite = cc.Sprite.create(cache.
getSpriteFrame("crate.png"));
crateSprite.setPosition(165+25*j,185-25*i);
crateSprite.setScale(5);
this.addChild(crateSprite);
cratesArray[i][j]=crateSprite;
break;
default:
cratesArray[i][j]=null;
}
}
}
}
});
Жесты и касания:
var listener = cc.EventListener.create({
event: cc.EventListener.TOUCH_ONE_BY_ONE,
swallowTouches: true,
onTouchBegan:function (touch,event) {
startTouch = touch.getLocation();
return true;
},
onTouchEnded:function(touch, event){
endTouch = touch.getLocation();
swipeDirection();
}
});
swipeTolerance is the minimum allowed distance in pixels between startTouch and endTouch in order to consider the whole action as a swipe.
Заметьте, что функции болжны возвращать истину, т.е. return true
function swipeDirection(){
var distX = startTouch.x - endTouch.x;
var distY = startTouch.y - endTouch.y;
if(Math.abs(distX)+Math.abs(distY)>swipeTolerance){
if(Math.abs(distX)>Math.abs(distY)){
if(distX>0){
playerSprite.setPosition(playerSprite.getPosition().
x-25,playerSprite.getPosition().y);
//move(-1,0);
}
else{
playerSprite.setPosition(playerSprite.getPosition().
x+25,playerSprite.getPosition().y);
//move(1,0);
}
}
else{
if(distY>0){
playerSprite.setPosition(playerSprite.getPosition().
x,playerSprite.getPosition().y-25);
//move(0,1);
}
else{
playerSprite.setPosition(playerSprite.getPosition().
x,playerSprite.getPosition().y+25);
//move(0,-1);
}
}
}
}
Проверку движения и перемещение коробок можно сделать в отдельной функции:
function move(deltaX,deltaY){
switch(level[playerPosition.y+deltaY][playerPosition.x+deltaX]){
case 0:
case 2:
level[playerPosition.y][playerPosition.x]-=4;
playerPosition.x+=deltaX;
playerPosition.y+=deltaY;
level[playerPosition.y][playerPosition.x]+=4;
playerSprite.setPosition(165+25*playerPosition.x,185-
25*playerPosition.y);
break;
case 3:
case 5:
if(level[playerPosition.y+deltaY*2][playerPosition.x+deltaX*2]==0
|| level[playerPosition.y+deltaY*2][playerPosition.x+deltaX*2]==2){
level[playerPosition.y][playerPosition.x]-=4;
playerPosition.x+=deltaX;
playerPosition.y+=deltaY;
level[playerPosition.y][playerPosition.x]+=1;
playerSprite.setPosition(165+25*playerPosition.x,185-
25*playerPosition.y);
level[playerPosition.y+deltaY][playerPosition.x+deltaX]+=3;
var movingCrate = cratesArray[playerPosition.y]
[playerPosition.x];
movingCrate.setPosition(movingCrate.getPosition().
x+25*deltaX,movingCrate.getPosition().y-25*deltaY);
cratesArray[playerPosition.y+deltaY][playerPosition.
x+deltaX]=movingCrate;
cratesArray[playerPosition.y][playerPosition.x]=null;
}
break;
}
}
Пример игры с Box2D физикой:
Исходный код: https://github.com/noelyahan/cocos2djstut/tree/415694ebd4e58afd05855d45473d71ee9bb9a3ed/game-six
Не забудьте подключить "modules" : ["cocos2d","external"],
var res = {
brick1x1 : "res/brick1x1.png",
brick2x1 : "res/brick2x1.png",
brick3x1 : "res/brick3x1.png",
brick4x1 : "res/brick4x1.png",
brick4x2 : "res/brick4x2.png",
ground : "res/ground.png",
totem : "res/totem.png"
};
Box2D is a realistic physics engine that uses real-world units of measurement. This way, everything you will create in Box2D world will be measured in meters. So, we need to find a ratio between pixels and meters. In almost every project, the 1 meter = 30 pixels setting works fine and allows us to think and work in pixels without caring about the Box2D internal unit of measurement.
var world;
var worldScale = 30;
var gravity = new Box2D.Common.Math.b2Vec2(0, -10);
var world = new Box2D.Dynamics.b2World(gravity, true);
var HelloWorldScene = cc.Scene.extend({
onEnter : function() {
this._super();
gameLayer = new HelloWorldLayer();
gameLayer.init();
this.addChild(gameLayer);
}
});
HelloWorldLayer = cc.Layer.extend({
init : function() {
this._super();
var backgroundLayer = new cc.LayerGradient(cc.color(0xdf,0x9f,0x83,255), cc.color(0xfa,0xf7,0x9f,255));
this.addChild(backgroundLayer);
this.scheduleUpdate();
},
update:function(dt){
world.Step(dt,10,10);
console.log(world);
},
});
Then, we already know that a physics world has gravity. Here is how we define the gravity: var gravity = new Box2D.Common.Math.b2Vec2(0, -10);
you already know Cocos2d-JS has its origin coordinate in the bottom left of the stage; so, as long as you move from bottom to top, your y coordinate increases. On the other hand, Box2D works in the opposite way: as long as a physics body falls down; its y coordinate increases,
world = new Box2D.Dynamics.b2World(gravity, true); As you can see, the world has two arguments: the gravity variable we created before, and a Boolean flag to determine whether bodies can sleep. Normally, to save CPU time, physics bodies that don't receive hits and aren't affected by forces for some amount of time are put to sleep. This means they still exist in the Box2D world although their position isn't updated at each frame until they wake up because of some event such as a collision or a force applied to them.
world.Step(dt,10,10) The Step method advances the simulation for a certain amount of time, dt in this case and to be as accurate as possible, while the other two arguments represent the velocity and position iterations, respectively.
this.addBody(240,10,480,20,false,"assets/ground.png","ground");
• 240: This is the horizontal centre of the body, in pixels.
• 10: This is the vertical centre of the body, in pixels.
• 480: This is the body width, in pixels.
• 20: This is the body height, in pixels.
• false: This Boolean value determines whether the body is dynamic or not. We are building two kinds of bodies, dynamic bodies, which are affected by forces such as gravity and react to collisions, and static bodies, which can't be moved. This will be a static body.
• "assets/ground.png": These are the graphic assets to be bound to the body.
• "ground": This is the body type. We call it ground because it will represent the ground.
var fixtureDef = new Box2D.Dynamics.b2FixtureDef; - Think about a fixture as a relationship between a body, which is the physics actor, and its shape, which determines how the body looks—like a box, like a circle, and so on.
fixtureDef.density = 1.0; fixtureDef.friction = 0.5; fixtureDef.restitution = 0.2; - The density attribute affects the mass of the body, friction determines how bodies slide along each other, and restitution is used to see how a body bounces.
fixtureDef.shape = new Box2D.Collision.Shapes.b2PolygonShape; fixtureDef.shape.SetAsBox(0.5*width/worldScale,0.5*height/worldScale); - The SetAsBox method creates a box given a width and a height that Box2D accepts as half of the actual width and height. So, if you want a box with a width of 30 meters, you'll have to set its width to 30*0.5.
var world;
var worldScale = 30;
var gravity = new Box2D.Common.Math.b2Vec2(0, -10);
var world = new Box2D.Dynamics.b2World(gravity, true);
var HelloWorldScene = cc.Scene.extend({
onEnter : function() {
this._super();
gameLayer = new HelloWorldLayer();
gameLayer.init();
this.addChild(gameLayer);
}
});
HelloWorldLayer = cc.Layer.extend({
init : function() {
this._super();
var backgroundLayer = new cc.LayerGradient(cc.color(0xdf,0x9f,0x83,255), cc.color(0xfa,0xf7,0x9f,255));
this.addChild(backgroundLayer);
this.scheduleUpdate();
this.addBody(10,10,cc.winSize.width,20,false,res.ground,"ground");
},
update:function(dt){
world.Step(dt,10,10);
console.log(world);
},
addBody: function (posX,posY,width,height,isDynamic,spriteImage,type) {
var fixtureDef = new Box2D.Dynamics.b2FixtureDef;
fixtureDef.density = 1.0;
fixtureDef.friction = 0.5;
fixtureDef.restitution = 0.2;
fixtureDef.shape = new Box2D.Collision.Shapes.b2PolygonShape;
fixtureDef.shape.SetAsBox(0.5 * width/worldScale,0.5 * height / worldScale);
var bodyDef = new Box2D.Dynamics.b2BodyDef;
if(isDynamic){
bodyDef.type = Box2D.Dynamics.b2Body.b2_dynamicBody;
}
else{
bodyDef.type = Box2D.Dynamics.b2Body.b2_staticBody;
}
bodyDef.position.Set(posX/worldScale,posY/worldScale);
var userSprite = cc.Sprite.create(spriteImage);
// TODO: userSprite.setScaleX((cc.winSize.width / 2 / background.getContentSize().width)*2); userSprite.setScaleY((cc.winSize.height / background.getContentSize().height) * 1);
this.addChild(userSprite, 0);
userSprite.setPosition(posX,posY);
bodyDef.userData = {
type: type,
asset: userSprite
}
var body = world.CreateBody(bodyDef)
body.CreateFixture(fixtureDef);
}
});
Обновление игровой сцены:
var world;
var worldScale = 30;
var gravity = new Box2D.Common.Math.b2Vec2(0, -10);
var world = new Box2D.Dynamics.b2World(gravity, true);
var HelloWorldScene = cc.Scene.extend({
onEnter : function() {
this._super();
gameLayer = new HelloWorldLayer();
gameLayer.init();
this.addChild(gameLayer);
}
});
HelloWorldLayer = cc.Layer.extend({
init : function() {
this._super();
var backgroundLayer = new cc.LayerGradient(cc.color(0xdf,0x9f,0x83,255), cc.color(0xfa,0xf7,0x9f,255));
this.addChild(backgroundLayer);
this.scheduleUpdate();
this.addBody(10,10,cc.winSize.width,20,false,res.ground,"ground");
//this.addBody(204,32,24,24,true, res.brick1x1, "destroyable");
this.addBody(276,32,24,24,true, res.brick1x1, "destroyable");
this.addBody(240,56,96,24,true, res.brick4x1, "destroyable");
this.addBody(240,80,48,24,true, res.brick2x1,"solid");
this.addBody(228,104,72,24,true,res.brick3x1, "destroyable");
this.addBody(240,140,96,48,true,res.brick4x2,"solid");
this.addBody(240,188,24,48,true,res.totem,"totem");
},
update:function(dt){
world.Step(dt,10,10);
for (var b = world.GetBodyList(); b; b = b.GetNext()) {
if (b.GetUserData() != null) {
var mySprite = b.GetUserData().asset;
mySprite.setPosition(b.GetPosition().x * worldScale,
b.GetPosition().y * worldScale);
mySprite.setRotation(-1 * cc.radiansToDegrees (b.GetAngle()));
}
}
},
addBody: function (posX,posY,width,height,isDynamic,spriteImage,type) {
var fixtureDef = new Box2D.Dynamics.b2FixtureDef;
fixtureDef.density = 1.0;
fixtureDef.friction = 0.5;
fixtureDef.restitution = 0.2;
fixtureDef.shape = new Box2D.Collision.Shapes.b2PolygonShape;
fixtureDef.shape.SetAsBox(0.5 * width/worldScale,0.5 * height / worldScale);
var bodyDef = new Box2D.Dynamics.b2BodyDef;
if(isDynamic){
bodyDef.type = Box2D.Dynamics.b2Body.b2_dynamicBody;
}
else{
bodyDef.type = Box2D.Dynamics.b2Body.b2_staticBody;
}
bodyDef.position.Set(posX/worldScale,posY/worldScale);
var userSprite = cc.Sprite.create(spriteImage);
// TODO: userSprite.setScaleX((cc.winSize.width / 2 / background.getContentSize().width)*2); userSprite.setScaleY((cc.winSize.height / background.getContentSize().height) * 1);
this.addChild(userSprite, 0);
userSprite.setPosition(posX,posY);
bodyDef.userData = {
type: type,
asset: userSprite
}
var body = world.CreateBody(bodyDef)
body.CreateFixture(fixtureDef);
}
});
Удаление элемента при нажатии и преобразование координат:
cc.eventManager.addListener(touchListener, this);
// ...
var touchListener = cc.EventListener.create({
event: cc.EventListener.TOUCH_ONE_BY_ONE,
swallowTouches: true,
onTouchBegan: function (touch, event) {
var worldPoint = new Box2D.Common.Math.b2Vec2(touch.
getLocation().x/worldScale,touch.getLocation().y/worldScale);
for (var b = world.GetBodyList(); b; b = b.GetNext()) {
if (b.GetUserData() != null && b.GetUserData().
type=="destroyable") {
for(var f = b.GetFixtureList();f; f=f.GetNext()){
if(f.TestPoint(worldPoint)){
gameLayer.removeChild(b.GetUserData().asset)
world.DestroyBody(b);
}
}
}
}
}
});
Проверка столкновения тотема с землей:
update:function(dt){
world.Step(dt,10,10);
for (var b = world.GetBodyList(); b; b = b.GetNext()) {
if (b.GetUserData() != null) {
var mySprite = b.GetUserData().asset;
mySprite.setPosition(b.GetPosition().x * worldScale,
b.GetPosition().y * worldScale);
mySprite.setRotation(-1 * cc.radiansToDegrees (b.GetAngle()));
if(b.GetUserData().type=="totem"){
for(var c = b.GetContactList(); c; c = c.m_next){
if(c.other.GetUserData() && c.other.GetUserData().
type=="ground"){
console.log("Oh no!!!!");
}
}
}
}
}
},
Пример игры с тотемом на Chipmunk2D
Изменения:
update:function(dt){
world.step(dt);
}
Для отладки:
var debugDraw = cc.PhysicsDebugNode.create(world);
debugDraw.setVisible(true);
this.addChild(debugDraw);
Добавление элементов:
addBody: function(posX,posY,width,height,isDynamic,spriteImage,type){
if(isDynamic){
var body = new cp.Body(1,cp.momentForBox(1,width,height));
}
else{
var body = new cp.Body(Infinity,Infinity);
}
body.setPos(cp.v(posX,posY));
if(isDynamic){
world.addBody(body);
}
var shape = new cp.BoxShape(body, width, height);
shape.setFriction(1);
shape.setElasticity(0);
shape.name=type;
world.addShape(shape);
}
cp.Body method, whose arguments are the mass and the moment of inertia http://en.wikipedia.org/wiki/Moment_of_inertia . So, a box with mass = 1 will be declared this way: var body = new cp.Body(1,cp.momentForBox(1,width,height));
Изменим способ удаления элементов:
for(var i=shapeArray.length-1;i>=0;i--){
if(shapeArray[i].pointQuery(cp.v(touch.getLocation().
x,touch.getLocation().y))!=undefined){
if(shapeArray[i].name=="destroyable"){
gameLayer.removeChild(shapeArray[i].image);
world.removeBody(shapeArray[i].getBody())
world.removeShape(shapeArray[i])
shapeArray.splice(i,1);
}
}
}
Измененим провеорку столкновений:
world.setDefaultCollisionHandler
(this.collisionBegin,null,null,null);
setDefaultCollisionHandler: This method will call four functions each time a collision will be updated. In Chipmunk2D as well as in Box2D, a collision has four states: ° begin: This method defines the time the script realizes that two shapes are touching. ° preSolve: This method is called just before solving the collision. To solve a collision means to update shapes and bodies according to the collision itself. ° postSolve: This method is called just after solving the collision. ° separate: This method is called when the collision ceases to exist—that is, these two shapes are no longer in touch.
collisionBegin : function (arbiter, space ) {
if((arbiter.a.name=="totem" && arbiter.b.name=="ground") ||
(arbiter.b.name=="totem" && arbiter.a.name=="ground")){
console.log("Oh no!!!!");
}
return true;
}
Добавим спрайты:
body.setPos(cp.v(posX,posY));
var bodySprite = cc.Sprite.create(spriteImage);
gameLayer.addChild(bodySprite,0);
bodySprite.setPosition(posX,posY);
if(isDynamic){
world.addBody(body);
}
var shape = new cp.BoxShape(body, width, height);
shape.setFriction(1);
shape.setElasticity(0);
shape.name=type;
shape.image=bodySprite;
Обновление позиции спрайтов:
update:function(dt){
world.step(dt);
for(var i=shapeArray.length-1;i>=0;i--){
shapeArray[i].image.x=shapeArray[i].body.p.x
shapeArray[i].image.y=shapeArray[i].body.p.y
var angle = Math.atan2(-shapeArray[i].body.rot.y,shapeArray[i].
body.rot.x);
shapeArray[i].image.rotation= angle*57.2957795;
}
}
Результат:
var world = new cp.Space();
var shapeArray=[];
world.gravity = cp.v(0, -100);
var touchListener = cc.EventListener.create({
event: cc.EventListener.TOUCH_ONE_BY_ONE,
swallowTouches: true,
onTouchBegan: function (touch, event) {
for(var i=shapeArray.length-1;i>=0;i--){
if(shapeArray[i].pointQuery(cp.v(touch.getLocation().
x,touch.getLocation().y))!=undefined){
if(shapeArray[i].name=="destroyable"){
gameLayer.removeChild(shapeArray[i].image);
world.removeBody(shapeArray[i].getBody())
world.removeShape(shapeArray[i])
shapeArray.splice(i,1);
}
}
}
}
});
var HelloWorldScene = cc.Scene.extend({
onEnter : function() {
this._super();
gameLayer = new HelloWorldLayer();
gameLayer.init();
this.addChild(gameLayer);
}
});
HelloWorldLayer = cc.Layer.extend({
init : function() {
this._super();
var backgroundLayer = new cc.LayerGradient(cc.color(0xdf,0x9f,0x83,255), cc.color(0xfa,0xf7,0x9f,255));
this.addChild(backgroundLayer);
var debugDraw = cc.PhysicsDebugNode.create(world);
debugDraw.setVisible(true);
this.addChild(debugDraw);
this.scheduleUpdate();
this.addBody(10,10,cc.winSize.width,20,false,res.ground,"ground");
this.addBody(204,32,24,24,true, res.brick1x1, "destroyable");
this.addBody(276,32,24,24,true, res.brick1x1, "destroyable");
this.addBody(240,56,96,24,true, res.brick4x1, "destroyable");
this.addBody(240,80,48,24,true, res.brick2x1,"solid");
this.addBody(228,104,72,24,true,res.brick3x1, "destroyable");
this.addBody(240,140,96,48,true,res.brick4x2,"solid");
this.addBody(240,188,24,48,true,res.totem,"totem");
cc.eventManager.addListener(touchListener, this);
world.setDefaultCollisionHandler(this.collisionBegin,null,null,null);
},
collisionBegin : function (arbiter, space ) {
if((arbiter.a.name=="totem" && arbiter.b.name=="ground") ||
(arbiter.b.name=="totem" && arbiter.a.name=="ground")){
console.log("Oh no!!!!");
}
return true;
},
update:function(dt){
world.step(dt);
world.step(dt);
for(var i=shapeArray.length-1;i>=0;i--){
shapeArray[i].image.x=shapeArray[i].body.p.x
shapeArray[i].image.y=shapeArray[i].body.p.y
var angle = Math.atan2(-shapeArray[i].body.rot.y,shapeArray[i].
body.rot.x);
shapeArray[i].image.rotation= angle*57.2957795;
}
},
addBody: function (posX,posY,width,height,isDynamic,spriteImage,type) {
if(isDynamic){
var body = new cp.Body(1,cp.momentForBox(1,width,height));
}
else{
var body = new cp.Body(Infinity,Infinity);
}
body.setPos(cp.v(posX,posY));
var bodySprite = cc.Sprite.create(spriteImage);
gameLayer.addChild(bodySprite,0);
bodySprite.setPosition(posX,posY);
if(isDynamic){
world.addBody(body);
}
var shape = new cp.BoxShape(body, width, height);
shape.setFriction(1);
shape.setElasticity(0);
shape.name=type;
shape.image=bodySprite;
world.addShape(shape);
shapeArray.push(shape);
}
});
Игра несколько в ряд.
Пример игры: http://www.mindjolt.com/globez.html
Ресурсы игры: https://github.com/chizhovdee/Match3-Cocos2dJs или https://github.com/chizhovdee/Match3Vol2
var res = {
globes_png : "res/globes.png",
globes_plist : "res/globes.plist",
};
Переменные:
var fieldSize = 6;
var tileTypes = ["red", "green", "blue", "grey", "yellow"];
var tileSize = 50;
var tileArray = [];
var globezLayer;
• fieldSize: This variable is the width and height of the field size, in tiles. This means we will play on a 6 x 6 tile field.
• tileTypes: This is an array with the keys of the sprites defined in the globes.plist file. I used only five different kinds of globez because I like the game to offer the opportunity to make big combos. You can choose how many colors you want; just keep in mind the more the colors in the game, the harder the gameplay.
tileSize: This variable is the size of a tile, in pixels.
• tileArray: This is the array that will contain all globez objects.
• globezLayer: This variable will be the layer where globez tiles will be placed.
Создание уровня:
var fieldSize = 6;
var tileTypes = ["red", "green", "blue", "grey", "yellow"];
var tileSize = 50;
var tileArray = [];
var globezLayer;
var HelloWorldScene = cc.Scene.extend({
onEnter : function() {
this._super();
gameLayer = new HelloWorldLayer();
gameLayer.init();
this.addChild(gameLayer);
}
});
HelloWorldLayer = cc.Layer.extend({
init : function() {
this._super();
var backgroundLayer = new cc.LayerGradient(cc.color(0xdf,0x9f,0x83,255), cc.color(0xfa,0xf7,0x9f,255));
this.addChild(backgroundLayer);
cc.spriteFrameCache.addSpriteFrames(res.globes_plist, res.globes_png);
globezLayer = new cc.Layer();
this.addChild(globezLayer)
this.createLevel();
},
createLevel: function(){
for(var i = 0; i < fieldSize; i ++) {
tileArray[i] = [];
for (var j = 0; j < fieldSize; j++) {
this.addTile(i, j);
}
}
},
addTile:function(row,col){
var randomTile = Math.floor(Math.random() * tileTypes.length);
var spriteFrame = cc.spriteFrameCache.getSpriteFrame(tileTypes[randomTile]);
var sprite = new cc.Sprite(spriteFrame);
sprite.val = randomTile;
sprite.picked = false;
globezLayer.addChild(sprite,0);
sprite.setPosition(col*tileSize+tileSize/2,row*tileSize+tileSize/2);
tileArray[row][col] = sprite;
}
});
visitedTiles is the array that will store the tiles once they have been picked up by the player, while startColor is the color of the first tile selected. We start with null as no color has been selected.
Проверка расстояния до центра. Доступный радиус в квадрате = tolerance.
We can say we have a legal move when:
• We are inside a tolerance area
• The current globe hasn't already been picked—the picked attribute is false
• The current globe is adjacent to the last picked globe
• The current globe has the same color as the first picked globe
Возвращение касания назад: To check for backtrack, you must check whether:
• We are inside a tolerance area
• The current globe has already been picked—the picked attribute is true
• The current globe is the second last entry in the visitedTiles array
var fieldSize = 6;
var tileTypes = ["red", "green", "blue", "grey", "yellow"];
var tileSize = 50;
var tileArray = [];
var globezLayer;
var startColor = null;
var visitedTiles = [];
var tolerance = 400;
var touchListener = cc.EventListener.create({
event: cc.EventListener.MOUSE,
onMouseDown: function (event) {
var pickedRow = Math.floor(event._y / tileSize);
var pickedCol = Math.floor(event._x / tileSize);
tileArray[pickedRow][pickedCol].setOpacity(128);
tileArray[pickedRow][pickedCol].picked = true;
startColor = tileArray[pickedRow][pickedCol].val;
visitedTiles.push({
row: pickedRow,
col: pickedCol
});
},
onMouseUp: function(event){
startColor=null;
for(var i = 0; i < visitedTiles.length; i ++){
if(visitedTiles.length<3){
tileArray[visitedTiles[i].row][visitedTiles[i].col].setOpacity(255);
tileArray[visitedTiles[i].row][visitedTiles[i].col].picked=false;
}
else{
globezLayer.removeChild(tileArray[visitedTiles[i].row][visitedTiles[i].col]);
tileArray[visitedTiles[i].row][visitedTiles[i].col]=null;
}
}
visitedTiles = [];
},
onMouseMove: function(event){
if(startColor!=null){
var currentRow = Math.floor(event._y / tileSize);
var currentCol = Math.floor(event._x / tileSize);
var centerX = currentCol * tileSize + tileSize / 2;
var centerY = currentRow * tileSize + tileSize / 2;
var distX = event._x - centerX;
var distY = event._y - centerY;
if(distX * distX + distY * distY < tolerance)
{ // We are inside a tolerance area. without using square roots, to save CPU time.
if(!tileArray[currentRow][currentCol].picked)
{ // The current globe hasn't already been picked—the picked attribute is false
if( Math.abs(currentRow - visitedTiles[visitedTiles.length - 1].row) <= 1 && Math.abs(currentCol - visitedTiles[visitedTiles.length -1].col) <= 1)
{ // The current globe is adjacent to the last picked globe
if(tileArray[currentRow][currentCol].val==startColor)
{ // The current globe has the same color as the first picked globe
tileArray[currentRow][currentCol].setOpacity(128);
tileArray[currentRow][currentCol].picked=true;
visitedTiles.push({
row:currentRow,
col:currentCol
});
}
}
}
else
{ // The current globe has already been picked—the picked attribute is true
if (visitedTiles.length >= 2 && currentRow == visitedTiles[visitedTiles.length - 2].row && currentCol == visitedTiles[visitedTiles.length - 2].col)
{ // he current globe is the second last entry in the visitedTiles array
tileArray[visitedTiles[visitedTiles.length - 1].row][visitedTiles[visitedTiles.length - 1].col].setOpacity(255);
tileArray[visitedTiles[visitedTiles.length - 1].row][visitedTiles[visitedTiles.length - 1].col].picked = false;
visitedTiles.pop();
}
}
}
}
}
});
var HelloWorldScene = cc.Scene.extend({
onEnter : function() {
this._super();
gameLayer = new HelloWorldLayer();
gameLayer.init();
this.addChild(gameLayer);
}
});
HelloWorldLayer = cc.Layer.extend({
init : function() {
this._super();
var backgroundLayer = new cc.LayerGradient(cc.color(0xdf,0x9f,0x83,255), cc.color(0xfa,0xf7,0x9f,255));
this.addChild(backgroundLayer);
cc.spriteFrameCache.addSpriteFrames(res.globes_plist, res.globes_png);
globezLayer = new cc.Layer();
this.addChild(globezLayer)
this.createLevel();
cc.eventManager.addListener(touchListener, this);
},
createLevel: function(){
for(var i = 0; i < fieldSize; i ++) {
tileArray[i] = [];
for (var j = 0; j < fieldSize; j++) {
this.addTile(i, j);
}
}
},
addTile:function(row,col){
var randomTile = Math.floor(Math.random() * tileTypes.length);
var spriteFrame = cc.spriteFrameCache.getSpriteFrame(tileTypes[randomTile]);
var sprite = new cc.Sprite(spriteFrame);
sprite.val = randomTile;
sprite.picked = false;
globezLayer.addChild(sprite,0);
sprite.setPosition(col*tileSize+tileSize/2,row*tileSize+tileSize/2);
tileArray[row][col] = sprite;
}
});
Падение: Once you remove the globez, you will need to check whether there are globez with empty spaces below them, and make them fall down accordingly.
Создание новых: Creating new globez shares the same concept as making the globez fall. For each column, we count the number of empty places; this number is the number of globez we have to create. In order to create a smooth appearance, each globe will be created outside the top of the stage and an animation tween will place it in its right place.
var fallTile = function(row,col,height){
var randomTile = Math.floor(Math.random()*tileTypes.length);
var spriteFrame = cc.spriteFrameCache.
getSpriteFrame(tileTypes[randomTile]);
var sprite = cc.Sprite.createWithSpriteFrame(spriteFrame);
sprite.val = randomTile;
sprite.picked = false;
globezLayer.addChild(sprite,0);
sprite.setPosition
(col*tileSize+tileSize/2,(fieldSize+height)*tileSize);
var moveAction = cc.MoveTo.create(0.5, new cc.Point(col*tileSize+tileSize/2,row*tileSize+tileSize/2));
sprite.runAction(moveAction);
tileArray[row][col] = sprite;
}
var touchListener = cc.EventListener.create({
event: cc.EventListener.MOUSE,
onMouseDown: function (event) {
var pickedRow = Math.floor(event._y / tileSize);
var pickedCol = Math.floor(event._x / tileSize);
tileArray[pickedRow][pickedCol].setOpacity(128);
tileArray[pickedRow][pickedCol].picked = true;
startColor = tileArray[pickedRow][pickedCol].val;
visitedTiles.push({
row: pickedRow,
col: pickedCol
});
},
onMouseUp: function(event){
startColor=null;
for(var i = 0; i < visitedTiles.length; i ++){
if(visitedTiles.length<3){
tileArray[visitedTiles[i].row][visitedTiles[i].col].setOpacity(255);
tileArray[visitedTiles[i].row][visitedTiles[i].col].picked=false;
}
else{
globezLayer.removeChild(tileArray[visitedTiles[i].row][visitedTiles[i].col]);
tileArray[visitedTiles[i].row][visitedTiles[i].col]=null;
}
}
// Just falling down
if(visitedTiles.length>=3){
for(i = 1; i < fieldSize; i ++){ // starting from lowest (left bottom)
for(var j = 0; j < fieldSize; j ++){
if(tileArray[i][j] != null){
var holesBelow = 0; // The holesBelow variable will keep track of the empty spaces below a globe.
for(var k = i - 1; k >= 0; k --){
if(tileArray[k][j] == null){
holesBelow++;
}
}
if(holesBelow>0){
var moveAction = cc.MoveTo.create(0.5, new cc.Point(tileArray[i][j].x,tileArray[i][j].y-holesBelow*tileSize));
tileArray[i][j].runAction(moveAction);
tileArray[i - holesBelow][j] = tileArray[i][j]; // swap lowest hole data and current tile data
tileArray[i][j] = null;
}
}
}
}
// create new
for(i = 0; i < fieldSize; i ++){
for(j = fieldSize-1; j>=0; j --){
if(tileArray[j][i] != null){
break;
}
}
var missingGlobes = fieldSize-1-j;
if(missingGlobes>0){
for(j=0;j<missingGlobes;j++){
fallTile(fieldSize-j-1,i,missingGlobes-j) // fallTile method to create a new tile with the destination row, destination column, and falling height
}
}
}
}
visitedTiles = [];
},
onMouseMove: function(event){
if(startColor!=null){
var currentRow = Math.floor(event._y / tileSize);
var currentCol = Math.floor(event._x / tileSize);
var centerX = currentCol * tileSize + tileSize / 2;
var centerY = currentRow * tileSize + tileSize / 2;
var distX = event._x - centerX;
var distY = event._y - centerY;
if(distX * distX + distY * distY < tolerance)
{ // We are inside a tolerance area. without using square roots, to save CPU time.
if(!tileArray[currentRow][currentCol].picked)
{ // The current globe hasn't already been picked—the picked attribute is false
if( Math.abs(currentRow - visitedTiles[visitedTiles.length - 1].row) <= 1 && Math.abs(currentCol - visitedTiles[visitedTiles.length -1].col) <= 1)
{ // The current globe is adjacent to the last picked globe
if(tileArray[currentRow][currentCol].val==startColor)
{ // The current globe has the same color as the first picked globe
tileArray[currentRow][currentCol].setOpacity(128);
tileArray[currentRow][currentCol].picked=true;
visitedTiles.push({
row:currentRow,
col:currentCol
});
}
}
}
else
{ // The current globe has already been picked—the picked attribute is true
if (visitedTiles.length >= 2 && currentRow == visitedTiles[visitedTiles.length - 2].row && currentCol == visitedTiles[visitedTiles.length - 2].col)
{ // he current globe is the second last entry in the visitedTiles array
tileArray[visitedTiles[visitedTiles.length - 1].row][visitedTiles[visitedTiles.length - 1].col].setOpacity(255);
tileArray[visitedTiles[visitedTiles.length - 1].row][visitedTiles[visitedTiles.length - 1].col].picked = false;
visitedTiles.pop();
}
}
}
}
}
});
Рисование линии касания и результат:
var fieldSize = 6;
var tileTypes = ["red", "green", "blue", "grey", "yellow"];
var tileSize = 50;
var tileArray = [];
var globezLayer;
var startColor = null;
var visitedTiles = [];
var tolerance = 400;
var arrowsLayer;
var fallTile = function(row,col,height){
var randomTile = Math.floor(Math.random()*tileTypes.length);
var spriteFrame = cc.spriteFrameCache.
getSpriteFrame(tileTypes[randomTile]);
var sprite = new cc.Sprite(spriteFrame);
sprite.val = randomTile;
sprite.picked = false;
globezLayer.addChild(sprite,0);
sprite.setPosition
(col*tileSize+tileSize/2,(fieldSize+height)*tileSize);
var moveAction = cc.MoveTo.create(0.5, new cc.Point(col*tileSize+tileSize/2,row*tileSize+tileSize/2));
sprite.runAction(moveAction);
tileArray[row][col] = sprite;
};
var drawPath = function(){
arrowsLayer.clear();
if(visitedTiles.length>0){
for(var i=1;i<visitedTiles.length;i++){
arrowsLayer.drawSegment(
new cc.Point(visitedTiles[i-1].col*tileSize+tileSize/2, visitedTiles[i-1].row*tileSize+tileSize/2),
new cc.Point(visitedTiles[i].col*tileSize+tileSize/2, visitedTiles[i].row*tileSize+tileSize/2), 4,
cc.color(255, 255, 255, 255)
);
}
}
};
var touchListener = cc.EventListener.create({
event: cc.EventListener.MOUSE,
onMouseDown: function (event) {
var pickedRow = Math.floor(event._y / tileSize);
var pickedCol = Math.floor(event._x / tileSize);
tileArray[pickedRow][pickedCol].setOpacity(128);
tileArray[pickedRow][pickedCol].picked = true;
startColor = tileArray[pickedRow][pickedCol].val;
visitedTiles.push({
row: pickedRow,
col: pickedCol
});
},
onMouseUp: function(event){
arrowsLayer.clear();
startColor=null;
for(var i = 0; i < visitedTiles.length; i ++){
if(visitedTiles.length<3){
tileArray[visitedTiles[i].row][visitedTiles[i].col].setOpacity(255);
tileArray[visitedTiles[i].row][visitedTiles[i].col].picked=false;
}
else{
globezLayer.removeChild(tileArray[visitedTiles[i].row][visitedTiles[i].col]);
tileArray[visitedTiles[i].row][visitedTiles[i].col]=null;
}
}
// Just falling down
if(visitedTiles.length>=3){
for(i = 1; i < fieldSize; i ++){ // starting from lowest (left bottom)
for(var j = 0; j < fieldSize; j ++){
if(tileArray[i][j] != null){
var holesBelow = 0; // The holesBelow variable will keep track of the empty spaces below a globe.
for(var k = i - 1; k >= 0; k --){
if(tileArray[k][j] == null){
holesBelow++;
}
}
if(holesBelow>0){
var moveAction = cc.MoveTo.create(0.5, new cc.Point(tileArray[i][j].x,tileArray[i][j].y-holesBelow*tileSize));
tileArray[i][j].runAction(moveAction);
tileArray[i - holesBelow][j] = tileArray[i][j]; // swap lowest hole data and current tile data
tileArray[i][j] = null;
}
}
}
}
// create new
for(i = 0; i < fieldSize; i ++){
for(j = fieldSize-1; j>=0; j --){
if(tileArray[j][i] != null){
break;
}
}
var missingGlobes = fieldSize-1-j;
if(missingGlobes>0){
for(j=0;j<missingGlobes;j++){
fallTile(fieldSize-j-1,i,missingGlobes-j) // fallTile method to create a new tile with the destination row, destination column, and falling height
}
}
}
}
visitedTiles = [];
},
onMouseMove: function(event){
if(startColor!=null){
var currentRow = Math.floor(event._y / tileSize);
var currentCol = Math.floor(event._x / tileSize);
var centerX = currentCol * tileSize + tileSize / 2;
var centerY = currentRow * tileSize + tileSize / 2;
var distX = event._x - centerX;
var distY = event._y - centerY;
if(distX * distX + distY * distY < tolerance)
{ // We are inside a tolerance area. without using square roots, to save CPU time.
if(!tileArray[currentRow][currentCol].picked)
{ // The current globe hasn't already been picked—the picked attribute is false
if( Math.abs(currentRow - visitedTiles[visitedTiles.length - 1].row) <= 1 && Math.abs(currentCol - visitedTiles[visitedTiles.length -1].col) <= 1)
{ // The current globe is adjacent to the last picked globe
if(tileArray[currentRow][currentCol].val==startColor)
{ // The current globe has the same color as the first picked globe
tileArray[currentRow][currentCol].setOpacity(128);
tileArray[currentRow][currentCol].picked=true;
visitedTiles.push({
row:currentRow,
col:currentCol
});
}
}
}
else
{ // The current globe has already been picked—the picked attribute is true
if (visitedTiles.length >= 2 && currentRow == visitedTiles[visitedTiles.length - 2].row && currentCol == visitedTiles[visitedTiles.length - 2].col)
{ // he current globe is the second last entry in the visitedTiles array
tileArray[visitedTiles[visitedTiles.length - 1].row][visitedTiles[visitedTiles.length - 1].col].setOpacity(255);
tileArray[visitedTiles[visitedTiles.length - 1].row][visitedTiles[visitedTiles.length - 1].col].picked = false;
visitedTiles.pop();
}
}
drawPath();
}
}
}
});
var HelloWorldScene = cc.Scene.extend({
onEnter : function() {
this._super();
gameLayer = new HelloWorldLayer();
gameLayer.init();
this.addChild(gameLayer);
}
});
HelloWorldLayer = cc.Layer.extend({
init : function() {
this._super();
var backgroundLayer = new cc.LayerGradient(cc.color(0xdf,0x9f,0x83,255), cc.color(0xfa,0xf7,0x9f,255));
this.addChild(backgroundLayer);
cc.spriteFrameCache.addSpriteFrames(res.globes_plist, res.globes_png);
globezLayer = new cc.Layer();
this.addChild(globezLayer);
arrowsLayer = new cc.DrawNode();
this.addChild(arrowsLayer);
this.createLevel();
cc.eventManager.addListener(touchListener, this);
},
createLevel: function(){
for(var i = 0; i < fieldSize; i ++) {
tileArray[i] = [];
for (var j = 0; j < fieldSize; j++) {
this.addTile(i, j);
}
}
},
addTile:function(row,col){
var randomTile = Math.floor(Math.random() * tileTypes.length);
var spriteFrame = cc.spriteFrameCache.getSpriteFrame(tileTypes[randomTile]);
var sprite = new cc.Sprite(spriteFrame);
sprite.val = randomTile;
sprite.picked = false;
globezLayer.addChild(sprite,0);
sprite.setPosition(col*tileSize+tileSize/2,row*tileSize+tileSize/2);
tileArray[row][col] = sprite;
}
});
Parkour game
Уроки: http://www.cocos2d-x.org/docs/tutorials/javascript/javascript/index.html
В главе 6 заменить
var contentSize = this.sprite.getContentSize();
на
var contentSize = this.sprite.getTextureRect();
Исходный код: https://github.com/iTyran/Parkhour-src/tree/50bb4aca26f3bcfe7314983f4469bebe751abd4c/Parkour
В главе 7 если будут полосы на экране поменяйте
"renderMode" : 1,
Суть скроллинга: У игрока задана скорость бега в applyImpulse, когда игрок пройдет расстояние eyeX, то фон игры и игрок сдвигается на такое же расстояние. А именно: неподвижный ранее фон сдвигается на -eyeX и игрок сдвигается на -eyeX. В результате движется только фон и нам остается его повторять в checkAndReload.
Заметьте сохранение тегов:
if(typeof TagOfLayer == "undefined") {
var TagOfLayer = {};
TagOfLayer.background = 0;
TagOfLayer.Animation = 1;
TagOfLayer.Status = 2;
};
Это позволяет писать код вида:
this.gameLayer.addChild(new BackgroundLayer(), 0, TagOfLayer.background);
// ...
this.gameLayer.getChildByTag(TagOfLayer.background).setPosition(cc.p(-eyeX,0));
// ...
var animationLayer = this.getParent().getChildByTag(TagOfLayer.Animation);
Я изменил немного код определения прыжка: https://gist.github.com/derofim/3c7020048c3394837eea
magicBall
Игра на физике и шейдерах:
Код: https://github.com/shuidong/magicBall/
Демо: http://shuidong.github.io/magicBall/
Tiled tmx parkour game:
1) Находим или создаем текстуру тайлов. Мой пример: https://cloud.githubusercontent.com/assets/9510143/13634342/fad9ba8e-e605-11e5-8800-a00d7380db83.png взят из http://www.gameart2d.com/free-platformer-game-tileset.html
2) Упаковываем в TexturePacker. Мои найстройки чтоб не ругалась бесплатная версия (ширину и высоту меняем под себя): opt level 0, algorithm basic, В advanced убрать detect identical sprites и все платные пункты
3) Смотрим https://www.youtube.com/watch?v=ZwaomOYGuYo и создаем карту в Tiled Map Editor аналогично https://developer.tizen.org/development/articles/how-use-tiled-map-editor-cocos2d-html5?langredirect=1 и смотрим примеры https://github.com/cocos2d/cocos2d-x/blob/4ab1540ccc27185bb468d361fcfe6bd4d284b4f5/tests/js-tests/src/TileMapTest/TileMapTest.js. Читаем https://developer.blackberry.com/html5/documentation/v1_0/using_sprites_and_tile_maps.html . Смотрим документацию http://www.cocos2d-x.org/docs/api-ref/js/V3.8/symbols/cc.TMXTiledMap.html .
Открываем код .tmx файла и меняем путь к картинке ( например image source="map_01.png" ). Не забываем добавить в код resource.js файлы tmx и png ( map_tmx: "res/map.tmx", map_01_png: "res/map_01.png", ). Добавляем тайлы на сцену ( var map = new cc.TMXTiledMap(res.map_tmx); this.addChild(map, 0); )
Добавляем код карты аналогично моим примерам https://gist.github.com/derofim/9508a6f44d2612fd32a0 - много примеров!
Размер tmx карты в cocos ограничен (~128 тайлов). Для прокрутки может понадобиться разбить большую карту на части и загружать их постепенно.
Искусственный интеллект
http://wizardfu.com/book/cocos2d-x/artificial-intelligence/