Quantcast
Channel: CSDN博客推荐文章
Viewing all articles
Browse latest Browse all 35570

Cocos2d-x学习(二十七):Hello, CCMapView

$
0
0

移动平台下的游戏有着区别于其他平台独特的优势,比如携带便捷,基于地理位置的社交,触摸的用户体验等等。

公司正在开发的游戏也利用了地理位置的优势,开发了社交功能,即将附近的玩家显示出来,可以和他们对战,聊天等等,之前的做法是利用cocos2d-x模拟的地图,根据服务器下发的附近玩家的信息显示,光是手势就实现了两种(捏合和滑动这两种在地图上使用最多的手势),但是效果很不好,于是决定使用ios自带的地图框架,与cocos2d-x结合来实现!于是,一个简单的CCMapView诞生了!


1.结构

cocos2d-x 主要特点是跨平台,于是需要定义一个跨平台的接口,然后在不同平台上做不同实现(因为地图UI是基于系统的),今天主要以ios平台为例

回调函数指针: CCMapViewCallback

跨平台接口类: CCMapView

ios平台实现: CCMapViewIOS

ios平台对MKMapView的封装类: MapView


2.接口:

(1)回调函数:CCMapViewCallback

由于objective c和c++通信只能通过指针,所以我们需要定义通信的函数指针类型,于是我定义了两个接口,一个用于系统定位完成之后回调游戏,另一个用于选中地图中的annotation之后回调游戏(这种从ios到游戏的接口可能是比较复杂一点,相当于回调。至于从游戏调用ios则感觉顺畅的多,只要保存一个对应类型的指针即可)

#ifndef Cocos2dxMap_MapViewCallback_h
#define Cocos2dxMap_MapViewCallback_h

typedef void (*DidLocatedCallback)(double latitude, double longtitude);
typedef void (*SelectedPlayerCallback)(unsigned long long playerID);

#endif

(2)跨平台接口类:CCMapView

首先定义了一个附近玩家信息的结构体,这个结构体是我们在地图中显示annotation时需要的信息

typedef struct
{
    unsigned long long mPlayerID;
    bool mIsMan;
    std::string mPlayerName;
    float mLatitude;
    float mLongtitude;
} CCPlayerInMap;

然后定义了一个简单的接口类,用于封装系统的地图空间,使用了组合的方式,这里利用void*的特点,实现了跨平台

class CCMapView: public cocos2d::CCNode
{
    
public:
    CCMapView(cocos2d::CCRect const& frame);
    ~CCMapView();
    
public:
    void StartLocating();
    void StopLocating();
    // in meters
    void SetRadius(float radius);
    void AddPlayer(CCPlayerInMap const& ccPlayerInMap);
    void RemoveAllPlayers();
    
public:
    void SetPosition(cocos2d::CCPoint const& pos);
    
public:
    void SetDidLocatedCallback(DidLocatedCallback callback);
    void SetSelectedPlayerCallback(SelectedPlayerCallback callback);
    
private:
    void* mMapView;
    
};

3.实现

定义一个类MapView来继承MKMapView并且实现CLLocationManager和MKMapViewDelegate协议(原谅我比较懒并且对objective-c的语法没啥自信吧,分开实现或许是更好的选择。。。),继承MKMapView是为了使用ios原生地图控件,实现CLLocationManager和MKMapViewDelegate协议是为了提高定位和地图显示和交互的回调接口!

@interface MapView : MKMapView <CLLocationManagerDelegate, MKMapViewDelegate>
{
    // Location
    CLLocationManager* mLocationManager;

    float mRadius;
    // for refreshing
    NSMutableArray* mAnnoArray;
    
    // Data
    NSMutableArray* mNearbyPlayers;
    
    DidLocatedCallback mDidLocatedCallback;
    SelectedPlayerCallback mSelectedPlayerCallback;
    
    UIImage* mMaleImage;
    UIImage* mMaleSelectedImage;
    UIImage* mFemaleImage;
    UIImage* mFemaleSelectedImage;
    
    MapAnnotationView* mPreMapAnnotionView;
}

@property (retain) CLLocationManager* mLocationManager;
@property (readwrite) float mRadius;
@property (nonatomic, retain) NSMutableArray* mAnnoArray;
@property (nonatomic, retain) NSMutableArray* mAnnoViewArr;
@property (nonatomic, retain) NSMutableArray* mNearbyPlayers;
@property (nonatomic, readwrite) DidLocatedCallback mDidLocatedCallback;
@property (nonatomic, readwrite) SelectedPlayerCallback mSelectedPlayerCallback;

- (void)StartLocating;
- (void)StopLocating;

- (void)AddNearbyPlayer:(PlayerInMap *) player;
- (void)RemoveAllPlayers;

- (void)HandleTap:(UIGestureRecognizer *)recognizer;

@end

在实现中,我运用了一个小技巧,绕过了大头针annotation不设置title就不能接受回调的显示,简单来说就是为每个MapAnnotationView添加一个Tap手势注册,而不是通过其自身的点击事件,其它的写法和一般的地图写法并没有太大差别,如果你的项目中没有这么强的定制性,可以添加一个title,就可以在didSelectedxxx函数中接收到点击回调了!

(ps:重点是在定位完成后和点击地图中的annotation调用设置好的函数指针回调游戏)

//
//  MapView.m
//  Cocos2dxMap
//
//  Created by quyou on 13-2-22.
//
//

#import "MapView.h"
#import "MapAnnotation.h"
#import "CCMapViewCallback.h"

@implementation MapView

@synthesize mLocationManager;
@synthesize mRadius;
@synthesize mAnnoArray;
@synthesize mAnnoViewArr;
@synthesize mNearbyPlayers;
@synthesize mDidLocatedCallback;
@synthesize mSelectedPlayerCallback;


- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
        // Initialization code
        self.mLocationManager = [[[CLLocationManager alloc] init] autorelease];
        if (![CLLocationManager locationServicesEnabled])
        {
            NSLog(@"User has opted out of location services!");
            return self;
        }
        
        mMaleImage = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"male" ofType:@"png"]];
        mMaleSelectedImage = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"male_selected" ofType:@"png"]];
        mFemaleImage = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"female" ofType:@"png"]];
        mFemaleSelectedImage = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"female_selected" ofType:@"png"]];
        
        mPreMapAnnotionView = nil;
        
        mAnnoArray = [[NSMutableArray alloc] init];
        
        self.mLocationManager.delegate = self;
        self.mLocationManager.desiredAccuracy = kCLLocationAccuracyBest;
        
        // in meters
        self.mLocationManager.distanceFilter = 5.0f;
        
        // init properties
        self.mRadius = 1000.0f;
        
        // Mapview delegate
        self.delegate = self;
    }
    return self;
}

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
    NSLog(@"Location manager error: %@", [error description]);
}

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    CLLocation* newLocation = [locations lastObject];
    if (newLocation != NULL)
    {
        NSDate* time = newLocation.timestamp;
        NSTimeInterval timePeriod = [time timeIntervalSinceNow];
        if(timePeriod < 2.0 )
        {
            //usually it take less than 0.5 sec to get a new location but you can use any value greater than 0.5 but i recommend 1.0 or 2.0
            // process the location
            MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(newLocation.coordinate, self.mRadius, self.mRadius);
            [self setRegion:region animated:YES];
            
            self.showsUserLocation = YES;
            self.userTrackingMode = YES;
            
            self.userLocation.title = @"oneRain";
            
            [self StopLocating];
            
            if (mDidLocatedCallback != NULL)
            {
                (*mDidLocatedCallback)(newLocation.coordinate.latitude, newLocation.coordinate.longitude);
            }
        }
    }
}

// Other information of location
// Pin
- (MKAnnotationView*) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
    MapAnnotationView* annotationView = nil;
    
    if (![annotation isKindOfClass:[MKUserLocation class]])
    {
        annotationView = [[MapAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"player"];
        
        // TODO classify by gender
        MapAnnotation* mapAnnotation = (MapAnnotation*) annotation;

        if (mapAnnotation.mIsMan)
        {
            annotationView.image = mMaleImage;
        }
        else
        {
            annotationView.image = mFemaleImage;
        }
        
        annotationView.annotation = annotation;
        
        UITapGestureRecognizer* tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(HandleTap:)];
        [annotationView addGestureRecognizer:tapGestureRecognizer];
        [tapGestureRecognizer release];
        
        [mAnnoViewArr addObject:annotationView];
    }
    
    return annotationView;
}

// Interface
- (void)StartLocating
{
    [self.mLocationManager startUpdatingLocation];
}

- (void)StopLocating
{
    [self.mLocationManager stopUpdatingLocation];
}

- (void)AddNearbyPlayer:(PlayerInMap *)player
{
    if (player == NULL)
        return;
    
    [mNearbyPlayers addObject:(id)player];
    // Add two test location
    MapAnnotation* anno = [[[MapAnnotation alloc] InitWithCoordinate: player->mLocation] autorelease];
    anno.mPlayerID = player->mPlayerID;
    anno.mIsMan = player->mIsMan;
    [self addAnnotation:anno];
    [mAnnoArray addObject:anno];
}

- (void)RemoveAllPlayers
{
    [self removeAnnotations:mAnnoArray];
}

// Handle tap event
- (void)HandleTap:(UIGestureRecognizer *)recognizer
{
    if ([recognizer.view isKindOfClass:[MapAnnotationView class]])
    {
        if (mPreMapAnnotionView != nil)
        {
            MapAnnotation* preMapAnno = (MapAnnotation*) mPreMapAnnotionView.annotation;
            
            mPreMapAnnotionView.image = preMapAnno.mIsMan ? mMaleImage : mFemaleImage;
        }
        
        MapAnnotationView* annoView = (MapAnnotationView*)(recognizer.view);
        MapAnnotation* anno = (MapAnnotation*) annoView.annotation;
        annoView.image = anno.mIsMan ? mMaleSelectedImage : mFemaleSelectedImage;
        mPreMapAnnotionView = annoView;
        
        if (mSelectedPlayerCallback != NULL)
        {
            (*mSelectedPlayerCallback)(anno.mPlayerID);
        }
    }
}

@end

4.关联

接口和ios本地代码都写完了,剩下的就是将这两部分关联起来了,在ios平台下,我用CCMapViewIOS来实现CCMapView接口类,思想是对游戏提供调用接口,并通过调用ios本地代码来实现游戏想要的操作!

//
//  CCMapViewIOS.mm
//  Cocos2dxMap
//
//  Created by quyou on 13-2-26.
//
//

#include "CCMapView.h"
#include "EAGLView.h"
#include "MapView.h"
#include "CCGeometry.h"
#include "CCDirector.h"

#define GetMapViewImpl ((MapView*)mMapView)

CCMapView::CCMapView(cocos2d::CCRect const& frame)
{
    mMapView = [[MapView alloc] initWithFrame:CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, frame.size.height)];
    setAnchorPoint(cocos2d::CCPointMake(0.5f, 0.5f));
    setContentSize(cocos2d::CCSizeMake(frame.size.width, frame.size.height));
    [[EAGLView sharedEGLView] addSubview:GetMapViewImpl];
}

CCMapView::~CCMapView()
{
}

void CCMapView::StartLocating()
{
    [GetMapViewImpl StartLocating];
}

void CCMapView::StopLocating()
{
    [GetMapViewImpl StopLocating];
}

void CCMapView::SetRadius(float radius)
{
    GetMapViewImpl.mRadius = radius;
}

void CCMapView::AddPlayer(const CCPlayerInMap &ccPlayerInMap)
{
    PlayerInMap playerInMap = { ccPlayerInMap.mPlayerID, ccPlayerInMap.mIsMan, 
    [NSString stringWithUTF8String:ccPlayerInMap.mPlayerName.c_str()],
    CLLocationCoordinate2DMake(ccPlayerInMap.mLatitude, ccPlayerInMap.mLongtitude)};
    [GetMapViewImpl AddNearbyPlayer:&playerInMap];
}

void CCMapView::RemoveAllPlayers()
{
    [GetMapViewImpl RemoveAllPlayers];
}

void CCMapView::SetPosition(const cocos2d::CCPoint &pos)
{
    CGRect frame = GetMapViewImpl.frame;
    frame.origin.x = pos.x - getContentSize().width * getAnchorPoint().x;
    frame.origin.y = cocos2d::CCDirector::sharedDirector()->getWinSize().height - (pos.y + getContentSize().height * (1 - getAnchorPoint().y));
    [GetMapViewImpl setFrame:frame];
}

void CCMapView::SetDidLocatedCallback(DidLocatedCallback callback)
{
    GetMapViewImpl.mDidLocatedCallback = callback;
}

void CCMapView::SetSelectedPlayerCallback(SelectedPlayerCallback callback)
{
    GetMapViewImpl.mSelectedPlayerCallback = callback;
}

剩余还有些零零碎碎的东西,比如自定制MapAnnotationView和MapAnnotation等等。。。




作者:oneRain88 发表于2013-3-1 23:37:48 原文链接
阅读:98 评论:0 查看评论

Viewing all articles
Browse latest Browse all 35570

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>