做了一个简单的 SceneKit 场景,即加载动画,并控制动画人物行走,保持摄像机跟随。。。。
最终效果:
git 传送门 : https://github.com/eassy/TimoRun

创建一个手柄控制板

这是控制板最终的样式,中间的按钮只会在触碰的时候显示出来,而且随着手指的移动,中间的按钮在大圈范围内跟随移动。
自定义一个 UIView ,利用 touchBegin 等一系列的事件检测方法,将触碰点捕捉到,同时判断触碰点是否在大圈范围内,在的话将按钮中心位置挪到触碰点处,不在的话,将按钮中心位置挪到跟触碰点在一个方向的大圈圆周这个点处。有难度的可能是计算按钮中心点最终所处的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
- (CGFloat)lengthFromA:(CGPoint)aPoint toB:(CGPoint)bPoint
{
return sqrt((aPoint.x - bPoint.x)*(aPoint.x-bPoint.x) + (aPoint.y-bPoint.y)*(aPoint.y-bPoint.y));
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.pad.hidden = NO;

CGPoint point = [[touches anyObject] locationInView:self];

[self movePadCenterToPoint:point animation:NO complete:^{

}];
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CGPoint point = [[touches anyObject] locationInView:self];

[self movePadCenterToPoint:point animation:NO complete:^{

}];
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{

[self movePadCenterToPoint:CGPointMake(_bigRadius, _bigRadius) animation:YES complete:^{
self.pad.hidden = YES;
}];

}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self movePadCenterToPoint:CGPointMake(_bigRadius, _bigRadius) animation:YES complete:^{
self.pad.hidden = YES;
}];
}

- (void)movePadCenterToPoint:(CGPoint)point animation:(BOOL)animation complete:(void(^)(void))completeBlock
{
CGFloat length = [self lengthFromA:point toB:CGPointMake(_bigRadius, _bigRadius)];
CGPoint finalPoint;
if (length < _bigRadius - _smallRadius) {
finalPoint = point;
}
else{

//计算应该的 center
CGFloat a = (point.x - _bigRadius)/(point.y - _bigRadius);
CGFloat l = _bigRadius - _smallRadius;

CGFloat offsetY = sqrt(l * l/(a * a + 1));
if (point.y >= _bigRadius) {
finalPoint.y = offsetY + _bigRadius;
}
else{
finalPoint.y = _bigRadius - offsetY;
}
finalPoint.x = a * (finalPoint.y - _bigRadius) + _bigRadius;
}

if (!isnan(finalPoint.x) && !isnan(finalPoint.y)) {
self.pad.center = finalPoint;
}

if (completeBlock) {
completeBlock();
}

if ([self.delegate respondsToSelector:@selector(padControl:toPoint:)]) {
[self.delegate padControl:self toPoint:CGPointMake(finalPoint.x - _bigRadius, finalPoint.y - _bigRadius)];
}
}

计算公式不难,大圆中心点位置为 (_bigRadius,_bigRadius),设大圆中心点为(x1,y1),触碰点位置为(x2,y2),最终点为(x,y),则可列公式:

1
2
3
4
5
6
7
//bigRadius:大圆半径  smallRadius:小圆半径
(x-x1)/(y-y1) = (x2-x1)/(y2-y1)
(x-x1)² + (y-y1)² = (_bigRadius - smallRadius)²
令 l = _bigRadius - _smallRadius;
a = (point.x - _bigRadius)/(point.y - _bigRadius);
由此可得 offsetY = |y-y1| = sqrt(l * l/(a * a + 1));
再根据触碰点相对于中心点的方向信息,得出 finalY,最终得出 finalX

加载文件中的动画和人物

因为资源文件是带动画的,所以取出来时要先 remove 掉,先保存,后 remove。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (void)makeTimo
{
SCNScene *timoScene = [SCNScene sceneNamed:@"art.scnassets/TiMo.DAE"];

self.timoNode = [SCNNode node];

for (SCNNode *childNode in timoScene.rootNode.childNodes) {

[self.timoNode addChildNode:childNode];
if (childNode.animationKeys.count) {
self.walkAnimation = [childNode animationForKey:childNode.animationKeys[0]];
self.walkAnimation.speed = 1.5;
[childNode removeAllAnimations];
}
}

self.timoNode.position = SCNVector3Make(0, 0.15, 0);
self.timoNode.scale = SCNVector3Make(0.2, 0.2, 0.2);

SCNPhysicsBody *timoBody = [SCNPhysicsBody kinematicBody];
timoBody.mass = 10;

self.timoNode.physicsBody = timoBody;
[self.mainScene.rootNode addChildNode:self.timoNode];

}

物理身体的类型我设置成了 kinematicBody ,因为 设置成动态身体的话根本站不住。。。软趴趴倒在地上哈哈哈。可以看到我将动画保存了起来,然后 remove 掉了。

创建 Camera 和灯光

有两个 Camera ,一个是位置一直跟随人物移动的 camera,一个是一直面向人物,但是位置不变的,通过双击屏幕切换这两个视角。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (void)setupCamera
{
// create and add a camera to the scene
SCNNode *cameraNode = [SCNNode node];
cameraNode.camera = [SCNCamera camera];
[self.mainScene.rootNode addChildNode:cameraNode];

// place the camera
cameraNode.position = SCNVector3Make(0, 50, 60);
cameraNode.rotation = SCNVector4Make(1, 0, 0, -M_PI / 6.f);
cameraNode.camera.zFar = 2000;
self.cameraNode = cameraNode;

((SCNView *)self.view).pointOfView = self.cameraNode;

SCNNode *thirdCameraNode = [SCNNode node];
thirdCameraNode.camera = [SCNCamera camera];
[self.mainScene.rootNode addChildNode:thirdCameraNode];
thirdCameraNode.position = SCNVector3Make(0, 50, 120);
thirdCameraNode.rotation = SCNVector4Make(1, 0, 0, -M_PI / 6.f);
thirdCameraNode.camera.zFar = 2000;
SCNLookAtConstraint *constraint = [SCNLookAtConstraint lookAtConstraintWithTarget:self.timoNode];
thirdCameraNode.constraints = @[constraint];
self.thirdCameraNode = thirdCameraNode;

}

初始的视角是跟随的摄像机。
我添加了一个环境光,八个聚焦灯(后面我超想用聚焦灯创建一个ktv那种球状灯的),灯的方向都是照向地面。

创建环境

创建地板

创建地板的时候并没有指定大小,好像默认无限大了?

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)makeFloor
{
SCNNode*floor = [SCNNode node];
floor.geometry = [SCNFloor floor];
floor.geometry.firstMaterial.diffuse.contents = @"art.scnassets/moss_diffuse.png";
floor.geometry.firstMaterial.diffuse.contentsTransform = SCNMatrix4MakeScale(2, 2, 1); //scale the wood texture
floor.geometry.firstMaterial.locksAmbientWithDiffuse = YES;

SCNPhysicsBody *staticBody = [SCNPhysicsBody staticBody];
floor.physicsBody = staticBody;

[self.mainScene.rootNode addChildNode:floor];
}

创建四面墙

创建两面墙,然后使用 clone ,rotate,position 等将墙放在合适的位置。每面墙都是静态躯体。

创建一个足球

创建一个球,设置动态物理身体,然后可以被踢着走了~