How to scroll vertical ScrollPane in horizontal ScrollPane properly? (LibGDX) - libgdx

I have made a horizontal pagedScrollPane according to tutorial here. Every page consists of the table with normal vertical ScrollPane.
I need to scroll vertically if initial drag was vertical and scroll between pages if drag was horizontal. I realised that I had to do myScroll.setCancelTouchFocus(false); with both scrollPanes to be able to activate the vertical scrollPane. Then I decided to setCancelTouchFocus to true if I want a scrollPane to disable.
scrollVert.addListener(new DragListener() {
#Override
public void drag(InputEvent event, float x, float y, int pointer) {
if (!scrolling) {
float dX = Math.abs(getDeltaX());
float dY = Math.abs(getDeltaY());
if (dX != 0 || dY != 0) {
if (dX > dY) {
scrollPages.setCancelTouchFocus(true);
} else {
scrollVert.setCancelTouchFocus(true);
}
scrolling = true;
}
}
super.drag(event, x, y, pointer);
}
#Override
public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
scrolling = false;
scrollPages.setCancelTouchFocus(false);
scrollVert.setCancelTouchFocus(false);
super.touchUp(event, x, y, pointer, button);
}
});
The problem is that if I drag diagonally (or fast right and down) the scrollPane jumps a little bit sideways and only then decides where to scroll.

Related

Libgdx GestureDirector pan acting strangely when dealing with multiple fingers

I'm creating a game where the movement mechanics are meant to work like this: tap/hold the right side of the screen to accelerate in the direction you're facing and swipe either left or right on the left side of the screen to rotate. It works well enough until the player tries to rotate with a second finger while accelerating with the first. The pan method seems to still run with the second finger but very infrequently and the gdx.input.getX(i) if statements don't fire off. I'm using the pan method of the GestureDetector class. Here's a video: https://www.youtube.com/watch?v=FmHI81ByPmU&feature=youtu.be
I looked at this similar question: libgdx multiple pan events but the answers did not work for me, setting pan to return false did nothing, and the controls aren't related to buttons so I can't change the method into touchDown
The pan method by itself:
#Override
public boolean pan(float x, float y, float deltaX, float deltaY) {
for(int i = 0 ; i < 2 ; i++){//In case they drag wit second finger
if(Gdx.input.isTouched(i) && Gdx.input.getX(i) < Gdx.graphics.getWidth() / 2){
if(Gdx.input.getDeltaX(i) < - 3){
directionListener.onRight();
System.out.println(">");
}
if(Gdx.input.getDeltaX(i) > 3) {
directionListener.onLeft();
System.out.println("<");
}
finger = i;//used to stop body from rotating when the finger rotating it is lifted
System.out.println(i + " = " + Gdx.input.getX(i));
}
}
//Can't replace with touchdown because it does not override method from superclass
return super.pan(x, y, deltaX, deltaY);
}
The entire class:
package com.doppelganger.spacesoccer.Helpers;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.input.GestureDetector;
public class SimpleDirectionGestureDetector extends GestureDetector{
public static int finger = 10;
public interface DirectionListener {
void onLeft();
void onRight();
}
public SimpleDirectionGestureDetector(DirectionListener directionListener) {
super(new DirectionGestureListener(directionListener));
}
private static class DirectionGestureListener extends GestureDetector.GestureAdapter {
DirectionListener directionListener;
DirectionGestureListener(DirectionListener directionListener){
this.directionListener = directionListener;
}
#Override
public boolean pan(float x, float y, float deltaX, float deltaY) {
for(int i = 0 ; i < 2 ; i++){//In case they drag with second finger
if(Gdx.input.isTouched(i) && Gdx.input.getX(i) < Gdx.graphics.getWidth() / 2){
if(Gdx.input.getDeltaX(i) < - 3){
directionListener.onRight();
System.out.println(">");
}
if(Gdx.input.getDeltaX(i) > 3) {
directionListener.onLeft();
System.out.println("<");
}
finger = i;//used to stop body from rotating when the finger rotating it is lifted
System.out.println(i + " = " + Gdx.input.getX(i));
}
}
//Can't replace with touchdown because it does not override method from superclass
return super.pan(x, y, deltaX, deltaY);
}
}
}
I see the problem, you are ignoring the input when you touch the right side of the screen. Here's the line:
if(Gdx.input.isTouched(i) && Gdx.input.getX(i) < Gdx.graphics.getWidth() / 2){
You could verify Gdx.input.getX(i) < Gdx.graphics.getWidth() / 2 inside on another if statement and add an else statement to check the right side, something like this:
if(Gdx.input.isTouched(i)) {
if (Gdx.input.getX(i) < Gdx.graphics.getWidth() / 2) {
// POINTER ON LEFT SIDE - SAME CODE
if(Gdx.input.getDeltaX(i) < - 3) {
directionListener.onRight();
System.out.println(">");
}
if(Gdx.input.getDeltaX(i) > 3) {
directionListener.onLeft();
System.out.println("<");
}
finger = i; //used to stop body from rotating when the finger rotating it is lifted
System.out.println(i + " = " + Gdx.input.getX(i));
} else {
// POINTER ON RIGHT SIDE - NEW CODE
System.out.println("Right side");
}
}

Rotate sprite according to onMouseMove cocos2d-x

img
auto spr= Sprite::create("spr.png");
spr->setPosition(Vec2(500, 500);
spr->setScale(0.2);
layer->addChild(gun, 1);
What do I need to do so my sprite can rotate my head according to the mouse position
void HelloWorld::onMouseMove(Event *event)
{
EventMouse* e = (EventMouse*)event;
(................)
}
1:
I think this is what you are trying to do:
const float PI = 3.1415;
void HelloWorld::onMouseMove(Event *event)
{
float dx = evnt->getCursorX() - spr->getPosition().x;
float dy = evnt->getCursorY() - spr->getPosition().y;
float rotation = (atan2(dx, dy)) * 180 / PI;
spr->setRotation(rotation);
}
I don't understand what you mean with "so my sprite can rotate my head" but that is the way to rotate a sprite, so apply it to whatever sprite you need.
Hope it helps! :D

How do I use mouselistener and press event to change a filled square to another color?

This is for homework, I have most of this done but I am stuck on the final step (implementing a mouse event that will change one of my randomized colored squares to become red instead of it's assigned colors) and am concerned that using the methods provided by my prof are not suitable to this, as he has us repaint after mouse event (which I feel my code will just override with more random colors). Any assistance or nudge in the right direction would be helpful, and I am sure this is a mess.
Update as per Camickr's assistance, my code has changed to the following:
import java.awt.GridLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.util.Random;
import javax.swing.*;
// MouseListener Imports
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
public class Test extends JPanel implements MouseListener
{
static Color[][] framework = new Color[8][8];
static int redCounter = 0;
// main Creates a JFrame and instantiates the 2d array of color objects
public static void main(String[] args)
{
// The frame handles all the outside window work
JFrame frame = new JFrame("MouseListener demo");
// Allows the 'X' in the upper right to cause the program to exit
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Create and set up the content pane.
// contentPane holds a panel
JComponent newContentPane = new Test();
// MouseListenerDemo isa JPanel isa JComponent
newContentPane.setOpaque(true); //content panes must be opaque
frame.setContentPane(newContentPane);
//Display the window.
frame.pack();
/* Causes this Window to be sized to fit the preferred size and layouts of its subcomponents.
The resulting width and height of the window are automatically enlarged if either of dimensions
is less than the minimum size as specified by the previous call to the setMinimumSize method.
If the window and/or its owner are not displayable yet, both of them are made displayable before
calculating the preferred size. The Window is validated after its size is being calculated.
*/
frame.setVisible(true);
for (int x = 0; x < framework.length; x++) {
for (int y = 0; y < framework.length; y++) {
Color rc = randomColor();
framework[x][y] = rc;
}
}
}
// Constructor
public Test()
{
// layout the panel with an area in which to draw
super(new GridLayout(0,1));
// get Graphics object, which allows you to draw on the panel
// set initial size of the panel
setPreferredSize(new Dimension(400, 500));
// ADD this JPanel to the list of components notified when a mouse event happens
this.addMouseListener(this);
// calls paint()
repaint();
}
// draws the screen
public void paint(Graphics g)
{
// Get the size
Dimension d = this.getSize();
int size = 50; // The edge length of the squares
int xSt = 50; // Starting x-coordinate
int ySt = 50; // Starting y-coordinate
int n = 8; // n X n board
int xPos, yPos;
for(int row = 0; row < framework.length; row ++){
for (int col = 0; col < framework.length; col ++){
xPos = xSt*col;
yPos = xSt*row;
// set color
g.setColor (framework[row][col]);
// Draw square
g.fillRect(xPos, yPos, size, size);
}
}
g.setColor(Color.black);
g.drawString("There are " + redCounter + " reds." , d.width/3, d.height);
}
public static Color randomColor(){
Random rg = new Random();
int result = rg.nextInt(12);
Color color;
switch(result){
case 0: color = Color.black;
break;
case 1: color = Color.blue;
break;
case 2: color = Color.cyan;
break;
case 3: color = Color.darkGray;
break;
case 4: color = Color.yellow;
break;
case 5: color = Color.green;
break;
case 6: color = Color.lightGray;
break;
case 7: color = Color.magenta;
break;
case 8: color = Color.orange;
break;
case 9: color = Color.pink;
break;
case 10: color = Color.red; redCounter = redCounter + 1;
break;
case 11: color = Color.white;
break;
default: color = Color.black;
break;
}
return color;
}
// MouseListener methods
public void mousePressed(MouseEvent evt)
{
// demonstrates how to use the parameter
// to get the position of the mouse press
Dimension d = this.getSize();
int x = evt.getX();
int y = evt.getY();
System.out.println(x+","+y);//these co-ords are relative to the component
System.out.println(evt.getSource());
for (int i = 0; i < framework.length; i++) {
for (int j = 0; j < framework.length; j++) {
System.out.println(framework[i][j]);
if (evt.getSource().equals(framework[i][j])) {
framework[i][j] = Color.red;
redCounter = redCounter + 1;
}
}
}
repaint(); // redisplay the frame by eventually calling the paint() method
}
// do nothing for the other mouse actions
public void mouseReleased(MouseEvent evt) {}
public void mouseClicked(MouseEvent evt) {}
public void mouseEntered(MouseEvent evt) {}
public void mouseExited(MouseEvent evt) {}
}
am concerned that using the methods provided by my prof are not suitable to this, as he has us repaint after mouse event (which I feel my code will just override with more random colors).
The repaint() in the MouseEvent is correct. However more random colors will be generated because your painting code is incorrect.
A painting method should only paint the state of your component, not change the state. Therefore:
You need to keep a data structure (lets say a 2D array) to hold the Color of each cell. In the constructor of your class you would then iterate through this array and assign the random colors to each entry in the array.
Then in the painting method you just iterate through the array and paint each cell using the color from the array.
Note you should be overriding paintComponent(), not paint() for the custom painting.
Then in your MouseListener code you just reset the Color in the Array for the cell that was clicked and invoke repaint().
I fixed it by getting the position of x/y and dividing them by 50 (my square height/width) and putting the int into the array index.
public void mousePressed(MouseEvent evt)
{
// demonstrates how to use the parameter
// to get the position of the mouse press
Dimension d = this.getSize();
int x = evt.getX()/50;
int y = evt.getY()/50;
framework[y][x] = Color.red;
redCounter = redCounter+1;
repaint(); // redisplay the frame by eventually calling the paint() method
}

Horizontal only touchpad in libgdx

Im trying to modify the libgdx Touchpad class to only be horizontal movement and not a full circle. So something like this:
But the touch is messed up and doesnt stop the movement within the bounds of the touchpad like it does for the round version. Heres my version, any help on where Im going wrong would be appreciated:
public class HorizontalTouchpad extends Widget {
private HorizontalTouchpadStyle style;
boolean touched;
boolean resetOnTouchUp = true;
private float deadzoneWidth;
private final Rectangle knobBounds = new Rectangle(0, 0, 0,0);
private final Rectangle touchBounds = new Rectangle(0, 0, 0,0);
private final Rectangle deadzoneBounds = new Rectangle(0, 0, 0,0);
private final Vector2 knobPosition = new Vector2();
private final Vector2 knobPercent = new Vector2();
/** #param deadzoneWidth The distance in pixels from the center of the touchpad required for the knob to be moved. */
public HorizontalTouchpad (float deadzoneWidth, Skin skin) {
this(deadzoneWidth, skin.get(HorizontalTouchpadStyle.class));
}
/** #param deadzoneWidth The distance in pixels from the center of the touchpad required for the knob to be moved. */
public HorizontalTouchpad (float deadzoneWidth, Skin skin, String styleName) {
this(deadzoneWidth, skin.get(styleName, HorizontalTouchpadStyle.class));
}
/** #param deadzoneWidth The distance in pixels from the center of the touchpad required for the knob to be moved. */
public HorizontalTouchpad (float deadzoneWidth, HorizontalTouchpadStyle style) {
if (deadzoneWidth < 0) throw new IllegalArgumentException("deadzoneWidth must be > 0");
this.deadzoneWidth = deadzoneWidth;
knobPosition.set(getWidth() / 2f, getHeight() / 2f);
setStyle(style);
setSize(getPrefWidth(), getPrefHeight());
addListener(new InputListener() {
#Override
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
if (touched) return false;
touched = true;
calculatePositionAndValue(x, y, false);
return true;
}
#Override
public void touchDragged (InputEvent event, float x, float y, int pointer) {
calculatePositionAndValue(x, y, false);
}
#Override
public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
touched = false;
calculatePositionAndValue(x, y, resetOnTouchUp);
}
});
}
void calculatePositionAndValue (float x, float y, boolean isTouchUp) {
float oldPositionX = knobPosition.x;
float oldPositionY = knobPosition.y;
float oldPercentX = knobPercent.x;
float oldPercentY = knobPercent.y;
float centerX = knobBounds.x;
float centerY = knobBounds.y;
knobPosition.set(centerX, centerY);
knobPercent.set(0f, 0f);
if (!isTouchUp) {
if (!deadzoneBounds.contains(x, y)) {
knobPercent.set((x - centerX) / (knobBounds.getWidth()/2), 0);
float length = knobPercent.len();
if (length > 1) knobPercent.scl(1 / length);
if (knobBounds.contains(x, y)) {
knobPosition.set(x, y);
} else {
knobPosition.set(knobPercent).nor().scl(knobBounds.getWidth()/2,0).add(knobBounds.x, knobBounds.y);
}
}
}
if (oldPercentX != knobPercent.x || oldPercentY != knobPercent.y) {
ChangeListener.ChangeEvent changeEvent = Pools.obtain(ChangeListener.ChangeEvent.class);
if (fire(changeEvent)) {
knobPercent.set(oldPercentX, oldPercentY);
knobPosition.set(oldPositionX, oldPositionY);
}
Pools.free(changeEvent);
}
}
public void setStyle (HorizontalTouchpadStyle style) {
if (style == null) throw new IllegalArgumentException("style cannot be null");
this.style = style;
invalidateHierarchy();
}
/** Returns the touchpad's style. Modifying the returned style may not have an effect until {#link #setStyle(HorizontalTouchpadStyle)} is
* called. */
public HorizontalTouchpadStyle getStyle () {
return style;
}
#Override
public Actor hit (float x, float y, boolean touchable) {
if (touchable && this.getTouchable() != Touchable.enabled) return null;
return touchBounds.contains(x, y) ? this : null;
}
#Override
public void layout () {
// Recalc pad and deadzone bounds
float halfWidth = getWidth() / 2;
float halfHeight = getHeight() / 2;
float radius = Math.min(halfWidth, halfHeight);
touchBounds.set(halfWidth, halfHeight, getWidth(),getHeight());
if (style.knob != null) radius -= Math.max(style.knob.getMinWidth(), style.knob.getMinHeight()) / 2;
knobBounds.set(halfWidth, halfHeight, getWidth(),getHeight());
deadzoneBounds.set(halfWidth, halfHeight, deadzoneWidth, getHeight());
// Recalc pad values and knob position
knobPosition.set(halfWidth, halfHeight);
knobPercent.set(0, 0);
}
#Override
public void draw (Batch batch, float parentAlpha) {
validate();
Color c = getColor();
batch.setColor(c.r, c.g, c.b, c.a * parentAlpha);
float x = getX();
float y = getY();
float w = getWidth();
float h = getHeight();
final Drawable bg = style.background;
if (bg != null) bg.draw(batch, x, y, w, h);
final Drawable knob = style.knob;
if (knob != null) {
x += knobPosition.x - knob.getMinWidth() / 2f;
y += knobPosition.y - knob.getMinHeight() / 2f;
knob.draw(batch, x, y, knob.getMinWidth(), knob.getMinHeight());
}
}
#Override
public float getPrefWidth () {
return style.background != null ? style.background.getMinWidth() : 0;
}
#Override
public float getPrefHeight () {
return style.background != null ? style.background.getMinHeight() : 0;
}
public boolean isTouched () {
return touched;
}
public boolean getResetOnTouchUp () {
return resetOnTouchUp;
}
/** #param reset Whether to reset the knob to the center on touch up. */
public void setResetOnTouchUp (boolean reset) {
this.resetOnTouchUp = reset;
}
/** #param deadzoneWidth The distance in pixels from the center of the touchpad required for the knob to be moved. */
public void setDeadzone (float deadzoneWidth) {
if (deadzoneWidth < 0) throw new IllegalArgumentException("deadzoneWidth must be > 0");
this.deadzoneWidth = deadzoneWidth;
invalidate();
}
/** Returns the x-position of the knob relative to the center of the widget. The positive direction is right. */
public float getKnobX () {
return knobPosition.x;
}
/** Returns the y-position of the knob relative to the center of the widget. The positive direction is up. */
public float getKnobY () {
return knobPosition.y;
}
/** Returns the x-position of the knob as a percentage from the center of the touchpad to the edge of the circular movement
* area. The positive direction is right. */
public float getKnobPercentX () {
return knobPercent.x;
}
/** Returns the y-position of the knob as a percentage from the center of the touchpad to the edge of the circular movement
* area. The positive direction is up. */
public float getKnobPercentY () {
return knobPercent.y;
}
/** The style for a {#link HorizontalTouchpad}.
* #author Josh Street */
public static class HorizontalTouchpadStyle {
/** Stretched in both directions. Optional. */
public Drawable background;
/** Optional. */
public Drawable knob;
public HorizontalTouchpadStyle () {
}
public HorizontalTouchpadStyle (Drawable background, Drawable knob) {
this.background = background;
this.knob = knob;
}
public HorizontalTouchpadStyle (HorizontalTouchpadStyle style) {
this.background = style.background;
this.knob = style.knob;
}
}
}
If you only allow horizontal movement it's only x-position that's interesting right? You can set y-position to always be a constant value. To make it stay within the bounds of the touchpad you need a check to make sure it doesn't move outside. So if you define some constants inside the layout method:
#Override
public void layout () {
// Recalc pad and deadzone bounds
float halfWidth = getWidth() / 2;
float halfHeight = getHeight() / 2;
float radius = Math.min(halfWidth, halfHeight);
touchBounds.set(halfWidth, halfHeight, getWidth(),getHeight());
if (style.knob != null) radius -= Math.max(style.knob.getMinWidth(), style.knob.getMinHeight()) / 2;
knobBounds.set(halfWidth, halfHeight, getWidth(),getHeight());
deadzoneBounds.set(halfWidth, halfHeight, deadzoneWidth, getHeight());
yPosition = halfHeight;
minX = style.knob.getMinWidth() / 2;
maxX = getWidth() - style.knob.getMinWidth() / 2;
// Recalc pad values and knob position
knobPosition.set(halfWidth, halfHeight);
knobPercent.set(0, 0);
}
and then when you set the position:
.....
if (knobBounds.contains(x, y)) {
if (x < minX) {
x = minX;
}
if (x > maxX) {
x = maxX;
}
knobPosition.set(x, yPosition);
} else {
knobPosition.set(knobPercent).nor().scl(knobBounds.getWidth()/2,0).add(knobBounds.x, knobBounds.y);
}
.....
Something like that (might need tweeking). You probably have to do a similair check for knobPercent.
Had to adjust the bounds and how it calculates the location and percent based on it being a rectangle versus a circle. Here is the updated layout method and calculate method:
#Override
public void layout () {
// Recalc pad and deadzone bounds
float halfWidth = getWidth() / 2;
float halfHeight = getHeight() / 2;
touchBounds.set(getX(), getY(), getWidth(),getHeight());
knobBounds.set(getX(), getY(), getWidth(),getHeight());
deadzoneBounds.set(halfWidth-deadzoneWidth/2, getY(), deadzoneWidth, getHeight());
yPosition = halfHeight;
// Recalc pad values and knob position
knobPosition.set(halfWidth, halfHeight);
knobPercent.set(0, 0);
}
Added a yPosition variable to make it easy to set the y position everytime
void calculatePositionAndValue (float x, float y, boolean isTouchUp) {
float oldPositionX = knobPosition.x;
float oldPositionY = knobPosition.y;
float oldPercentX = knobPercent.x;
float oldPercentY = knobPercent.y;
float centerX = knobBounds.width/2;
float centerY = knobBounds.height/2;
knobPosition.set(centerX, centerY);
knobPercent.set(0f, 0f);
if (!isTouchUp) {
if (!deadzoneBounds.contains(x, y)) {
knobPercent.set((x - centerX) / (knobBounds.getWidth()/2), 0);
float length = knobPercent.len();
if (length > 1) knobPercent.scl(1 / length);
if (knobBounds.contains(x, y)) {
knobPosition.set(x, yPosition);
} else {
knobPosition.set(knobPercent).scl(knobBounds.getWidth()/2,0).add(knobBounds.width/2, knobBounds.height/2);
}
}
}
if (oldPercentX != knobPercent.x || oldPercentY != knobPercent.y) {
ChangeListener.ChangeEvent changeEvent = Pools.obtain(ChangeListener.ChangeEvent.class);
if (fire(changeEvent)) {
knobPercent.set(oldPercentX, oldPercentY);
knobPosition.set(oldPositionX, oldPositionY);
}
Pools.free(changeEvent);
}
}
The big change here was calculating the percent and changing the position when not within the bounds. It was using getX() for the circle which is the center, but for rectangle it is the bottom left. So had to change it to the center.

How to make a sliding menu in cocos2dx in c++ for IOS game

I want to make a sliding menu just like the level menu in which on one screen there will be 40 sprites labelled as level 1 level 2 respectively up to 40.
At the bottom right there will be another sprite with a arrow to which when I click it should slide to other screen and show the levels 41 to 80.Please provide me with a basic concept how to use it.I will be thankful to you.
Note: I am using Xcode and ony want solution in cocos2d-x using c++
This is the way I have done this in the past...I had a game with the option for the player to select multiple space ships, 4 per page, with back/forward arrows on each page as well.
Create a CCScene derived class.
Place all your menu items, including the control arrows for ALL pages on it. You will have to space all the items so the items for the first page are on the visible part of the screen and the next group is 100% off to the right, the third group is 200% off the right, etc.
The control buttons on the scene start an action to move the layer 100% to the left (if they move right) or 100% to the right (if they move left).
All of these are attached to a single "Menu", which is what the actions are applied against. If you want, you can put the menu into a layer (so it has background that moves). This is up to you.
In the example Scene below, I just used a simple menu.
MainScene.h
#ifndef __MainScene__
#define __MainScene__
#include "cocos2d.h"
using namespace cocos2d;
class MainScene : public CCScene
{
private:
// This class follows the "create"/"autorelease" pattern.
// Private constructor.
MainScene();
CCMenu* _menu;
bool _sliding;
void MenuCallback(CCObject* sender);
void PageLeft();
void PageRight();
void SlidingDone();
protected:
// This is protected so that derived classes can call it
// in their create methods.
bool init();
private:
void CreateMenu();
public:
static MainScene* create();
~MainScene();
virtual void onEnter();
virtual void onExit();
virtual void onEnterTransitionDidFinish();
virtual void onExitTransitionDidStart();
};
#endif /* defined(__MainScene__) */
MainScene.cpp
#include "MainScene.h"
#define ARROW_LEFT (-1)
#define ARROW_RIGHT (-2)
#define MENU_ITEMS_ACROSS 4
#define MENU_ITEMS_DOWN 5
#define MENU_ITEMS_PAGE (MENU_ITEMS_ACROSS*MENU_ITEMS_DOWN)
#define MENU_ITEMS_TOTAL 50
#define MENU_PAGES ((MENU_ITEMS_TOTAL/MENU_ITEMS_PAGE)+1)
#define MENU_FRACTION (ccp(0.8,0.8))
#define MENU_ANCHOR (ccp(0.5,0.5))
#define SLIDE_DURATION 1.0
MainScene::MainScene() :
_menu(NULL)
_sliding(false)
{
}
MainScene::~MainScene()
{
}
static CCPoint CalculatePosition(int itemNum)
{
CCSize scrSize = CCDirector::sharedDirector()->getWinSize();
float Xs = scrSize.width;
float Ys = scrSize.height;
int gRows = MENU_ITEMS_DOWN;
int gCols = MENU_ITEMS_ACROSS;
int gBins = gRows*gCols;
float Xb = MENU_FRACTION.x*Xs/gCols;
float Yb = MENU_FRACTION.y*Ys/gRows;
float Xa = MENU_ANCHOR.x * Xs;
float Ya = MENU_ANCHOR.y * Ys;
int page = itemNum / gBins;
int binCol = itemNum % gCols;
int binRow = (itemNum-page*gBins) / gCols;
float xPos = binCol * Xb + Xb/2 + Xa - MENU_FRACTION.x*Xs/2 + page * Xs;
float yPos = Ya - binRow*Yb - Yb/2 + MENU_FRACTION.y * Ys/2;
CCPoint pos = ccp(xPos,yPos);
return pos;
}
void MainScene::CreateMenu()
{
if(_menu == NULL)
{
CCSize scrSize = CCDirector::sharedDirector()->getWinSize();
_menu = CCMenu::create();
_menu->setPosition(ccp(0,0));
addChild(_menu);
CCMenuItemFont* pItem;
CCPoint position;
// Create the next/back menu items.
for(int page = 0; page < MENU_PAGES; page++)
{
// Create the Back/Forward buttons for the page.
// Back arrow if there is a previous page.
if(page > 0)
{
pItem = CCMenuItemFont::create("Back", this, menu_selector(MainScene::MenuCallback));
pItem->setTag(ARROW_LEFT);
position = ccp(page*scrSize.width + scrSize.width*0.1,scrSize.height*0.1);
pItem->setPosition(position);
pItem->setFontSize(35);
pItem->setFontName("Arial");
_menu->addChild(pItem);
}
if(page < (MENU_PAGES-1))
{
pItem = CCMenuItemFont::create("Next", this, menu_selector(MainScene::MenuCallback));
pItem->setTag(ARROW_RIGHT);
position = ccp(page*scrSize.width + scrSize.width*0.9,scrSize.height*0.1);
pItem->setPosition(position);
pItem->setFontSize(35);
pItem->setFontName("Arial");
_menu->addChild(pItem);
}
}
// Create the actual items
for(int idx = 0; idx < MENU_ITEMS_TOTAL; idx++)
{
char buffer[256];
sprintf(buffer,"Item #%d",idx);
pItem = CCMenuItemFont::create(buffer, this, menu_selector(MainScene::MenuCallback));
pItem->setFontSize(35);
pItem->setFontName("Arial");
pItem->setTag(idx);
position = CalculatePosition(idx);
pItem->setPosition(position);
_menu->addChild(pItem);
}
}
}
bool MainScene::init()
{
return true;
}
MainScene* MainScene::create()
{
MainScene *pRet = new MainScene();
if (pRet && pRet->init())
{
pRet->autorelease();
return pRet;
}
else
{
CC_SAFE_DELETE(pRet);
return NULL;
}
}
void MainScene::onEnter()
{
CCScene::onEnter();
CreateMenu();
}
void MainScene::onExit()
{
CCScene::onExit();
}
void MainScene::onEnterTransitionDidFinish()
{
CCScene::onEnterTransitionDidFinish();
}
void MainScene::onExitTransitionDidStart()
{
CCScene::onExitTransitionDidStart();
}
void MainScene::SlidingDone()
{
_sliding = false;
}
void MainScene::PageLeft()
{
if(_sliding)
return;
_sliding = true;
CCSize scrSize = CCDirector::sharedDirector()->getWinSize();
CCFiniteTimeAction* act1 = CCMoveBy::create(SLIDE_DURATION, ccp(scrSize.width,0));
CCFiniteTimeAction* act2 = CCCallFunc::create(this, callfunc_selector(MainScene::SlidingDone));
_menu->runAction(CCSequence::create(act1,act2,NULL));
}
void MainScene::PageRight()
{
if(_sliding)
return;
_sliding = true;
CCSize scrSize = CCDirector::sharedDirector()->getWinSize();
CCFiniteTimeAction* act1 = CCMoveBy::create(SLIDE_DURATION, ccp(-scrSize.width,0));
CCFiniteTimeAction* act2 = CCCallFunc::create(this, callfunc_selector(MainScene::SlidingDone));
_menu->runAction(CCSequence::create(act1,act2,NULL));
}
void MainScene::MenuCallback(CCObject* sender)
{
// This is a very contrived example
// for handling the menu items.
// -1 ==> Left Arrow
// -2 ==> Right Arrow
// Anything else is a selection
CCMenuItem* pMenuItem = (CCMenuItem*)sender;
switch(pMenuItem->getTag())
{
case ARROW_LEFT:
PageLeft();
break;
case ARROW_RIGHT:
PageRight();
break;
default:
CCLOG("Got Item %d Pressed",pMenuItem->getTag());
break;
}
}
Note The formulas for getting the items spread across several pages can be a little tricky. There is a notion of "Screen fraction", which is how much the grid of items takes up on the page. There is also the notion of "menu anchor", which where on the page you want the grid to be.
Some screen shots
or you can do it the modern way with less code!!
// you have to include this header to use the ui classes
#include "ui/CocosGUI.h"
using namespace ui;
#define COLS 4
#define ROWS 4
#define ITEMS_PER_PAGE (ROWS * COLS)
#define TOTAL_PAGES_NUM 10
#define MENU_PADDING (Vec2(0.8,0.8))
#define MENU_ANCHOR (Vec2(0.5,0.5))
static Vec2 calcPosition(int itemNum)
{
Size scrSize = Director::getInstance()->getWinSize();
float Xs = scrSize.width;
float Ys = scrSize.height;
float Xb = MENU_PADDING.x*Xs / COLS;
float Yb = MENU_PADDING.y*Ys / ROWS;
float Xa = MENU_ANCHOR.x * Xs;
float Ya = MENU_ANCHOR.y * Ys;
int page = itemNum / ITEMS_PER_PAGE;
int binCol = itemNum % COLS;
int binRow = (itemNum - page * ITEMS_PER_PAGE) / COLS;
float xPos = binCol * Xb + Xb / 2 + Xa - MENU_PADDING.x*Xs / 2 + page * Xs;
float yPos = Ya - binRow*Yb - Yb / 2 + MENU_PADDING.y * Ys / 2;
return Vec2(xPos, yPos);
}
//init method
// pageView is the container that will contain all pages
auto pageView = PageView::create();
pageView->setContentSize(winSize);
//if you want pages indicator just uncomment this
//pageView->setIndicatorEnabled(true);
//pageView->setIndicatorPosition(some position);
//pageView->setIndicatorSelectedIndexColor(some Color3B);
for (int i = 0; i < TOTAL_PAGES_NUM; i++) {
auto layout = Layout::create();
layout->setContentSize(winSize);
// give each page a different random color
int r = rand() % 200;
int g = rand() % 200;
int b = rand() % 200;
auto bg = LayerColor::create(Color4B(Color3B(r, g, b)), winSize.width, winSize.height);
layout->addChild(bg, 0);
// populate each single page with items (which are in this case labels)
for (int i = 0; i < ITEMS_PER_PAGE; i++) {
auto label = LabelTTF::create(StringUtils::format("item %i", (i + 1)), "Comic Sans MS", 15);
Vec2 pos = calcPosition(i);
label->setPosition(pos);
layout->addChild(label, 1);
}
pageView->addPage(layout);
}
this->addChild(pageView);
I’ve modified existing one and uploaded in github. Here is the link:
GitHub Link to SlidingMenu
You may find it helpful. You can directly add it into your game.