一个ARPG框架的搭建(序)
发表于2014 年 1 月 27 日
之前用unity连demo都没做完整过,本次开始着手搭建这个ARPG框架的时候完全是一筹莫展,之前也没有过任何制作ARPG游戏的经验,所以一切都要从头开始。
我在这里只说明我的思考过程,这些东西基本没有借鉴过任何参考,所以会显得比较笨拙。
这个框架我是按照暗黑式的ARPG来处理的,也就是45度固定镜头,鼠标点击寻路,点击打怪。不过按照要求在里面加入了追尾视角,实现的不太好。
一个ARPG框架的搭建(一)
发表于2014 年 1 月 27 日
大概想了一下各种ARPG、RPG的实现,开始觉得挺简单。后来根据要求列了个表,把要实现的东西都大致列举了一下,还比较散:
1.角色控制;
2.摄像机;
3.战斗;
4.技能;
5.任务;
6.对象交互;
7.GUI;
8.剧情演绎;
9.……
够多了,完全不知道从何入手。
我大概知道RPG里面有很多道具,那不如就从道具开始,于是开始对RPG游戏里面的道具做了一个分类,大概是这样的:
于是根据这个写了一堆类:
?ItemBase:基类类型
?Item:道具基类
?LastItem:可持续使用道具
?UsedItem:消耗性道具
?OtherItem:其他类型道具,这个用于拓展
?StoryItem:剧情道具
?Armor:防具
?Adothins:装饰物
?Samllthing:小物件,用于拓展
?Weapon:武器
类设计好了,但是接下来的问题是:
虽然这里有一大堆类,但是怎么把他们驱动起来,成为一个游戏的一部分?
考虑到Unity3D执行脚本实例化的方式就是让指定脚本继承MonoBehaviour类,并将其挂到一个场景中的对象上。这些类融入游戏的问题解决了,但这还远远不够,接下来的几个问题是:
1.如何有秩序的驱动这些代码?
2.如何方便管理?
3.如何高效?
4.如何让后期开发易用?
这几个问题,想想简单但是说到底脑子里面还是一团浆糊,于是又是前途一片昏暗。
后来看了两眼前人的一个实现发现点眉目,他的方法主要是这样的:
在每个场景中都布置一些固有的部件:
我对这两个东西比较感兴趣:
[Harvestable Objects]
[MUST BE ON EVERY LEVEL]
第一个是可探索对象,应该就是一些可以掉落的资源的实现。
然后下一个东西非常重要,里面包含了一大堆东西:
?[AchievementHandler]
?[GameMaster]
?[GUI]
?[Item_Spawner]
?[MainCamera]
?[Respawn]
第一个就是传说中的成就处理器,游戏中所有的成就达成全部由此对象处理。
第二个是游戏master(不知道这个该怎么翻译,大概是规则一类的),里面包含了GlobalPrefabs.cs,GameMaster.cs,GameSetting.cs,看来这个是应该就是全局管理的脚本了。
接下来一个是GUI,如下图所示,几乎所有的和游戏有关的UI界面全部都由这个管理:
下一个是道具生成器,这个框架的所有道具貌似都是随机生成的不太符合我的要求我就没有多看了,还是习惯于自己的道具管理方法。
下一个主相机,这个必须有就不多说了。
最后是玩家的重生点,这个框架的玩家是运行时动态生成的,也就是说不需要在编辑器里面去放置玩家的位置,如果玩家死亡之后就会从这个出生点重生。暂时还不需要。
大概有个底了,然后开始去写,我首先就布置了一下几个脚本: _GameGUI.cs
用于全局GUI的管理
? _GameMaster.cs
游戏主要规则的管理
? _GameSetting.cs
游戏设置
? _Project.cs
工程相关的脚本
? dubug_test.cs
临时测试(个人恶趣味而已…)
最后我创建了一个Camera 文件夹干脆从已经确定要写的地方写起防止过于迷茫以至于毫无进度。这个摄像机主要是用来观察玩家的摄像机,主要的行为有:
?
跟随玩家移动,固定视角; ?
可以调节距离玩家的远近; ?
防止玩家被场景物体遮挡; ?
绕玩家固定高度旋转; ? 追尾和45度角互换;
需要说明的是,我是采用的摄像机拉近的方案来实现防止物体遮挡,玩家可以根据自己的情况来旋转摄像机改变观察角度,这个旋转类似于《仙剑奇侠传3》的摄像机模式。
备用方案:摄像机自动检测遮挡角度,然后自动旋转到没有遮挡的角度,如果全部遮挡就拉近。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 //PlayerCamera.cs
using UnityEngine ;
using System.Collections ;
public class PlayerCamera : MonoBehaviour
{
public bool myprint = true ;
public float distancelevel = 5;
public float maxdistance = 15;
public float mindistance = 5;
public Transform target ;
public float distance = 10.0f ;
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 public float height = 6.0f;
public float rotationspeed = 2f;
public float heightdamping = 2.0f; public float rotationdamping = 2.0f;
public LayerMask mask =-1;
public float offsetfromwall = 1f;
public bool bcatchview =false; public float cheight = 3.34f;
private static bool _bcatchview;
private Vector3 direction;
private float rotation = 0f;
private GameObject lastobj;
private Vector3 finalpos;
private Transform lookatpos;
public static bool bCatchView
{
get
{
return _bcatchview;
}
}
//private int levellength;
// Use this for initialization
void Start ()
{
if(!target)
target =
GameObject.FindGameObjectWithTag("Player").transform;
lookatpos = target.FindChild("Hair");
direction = Vector3.up+Vector3.left;
transform.position= target.position+ direction.normalized*distance;
transform.LookAt(target);
//levellength = distancelevel.Length;
//Debug.Log((Vector3.up+Vector3.left)*distance);
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99 10 0 10}
// Update is called once per frame
void Update ()
{
_bcatchview = bcatchview;
if(!bcatchview&&Input.GetMouseButtonUp(2))
{
distance += distancelevel;
if(distance>maxdistance)
distance = mindistance;
}
}
void LateUpdate()
{
if(!target)
{
Debug.Log("NOTE:There is no player!");
return;
}
var trueHeroPos = target.position+new
Vector3(0,1.5f,0);
// var camPos = transform.position;
var finalDist = distance;
if(!bcatchview)
{
if(Input.GetAxis("Mouse ScrollWheel")>0)
{
rotation =
rotationspeed*Time.deltaTime;
}
else if(Input.GetAxis("Mouse ScrollWheel")<0)
{
rotation =
-rotationspeed*Time.deltaTime;
}
}
else
10 2 10 3 10 4 10 5 10 6 10 7 10 8 10 9 11 0 11 1 11 2 11 3 11 4 11 5 11 6 11 7 11 8 11 9 12 0 12 1 12 2 12
rotation =0;
distance = cheight;
direction =
target.transform.TransformDirection(Vector3.back)+Vecto r3.up;
}
RaycastHit hit;
if( Physics.Linecast(trueHeroPos, trueHeroPos+direction.normalized*distance , out hit, mask.value))
{
string name =
hit.collider.gameObject.tag;
if(name !="MainCamera"&&name != "testpos"&&name!="Player")
finalDist = Vector3.Distance (trueHeroPos, hit.point)- offsetfromwall;
}
finalpos = target.position+
direction.normalized*finalDist +
transform.TransformDirection(Vector3.right)*rotation;
finalpos.y= target.position.y+
finalDist*Mathf.Sqrt(2)/2f;
transform.position= finalpos;
if(rotation!=0)
{
transform.LookAt(target);
direction =
transform.TransformDirection(Vector3.back);
}
else if(bcatchview)
{
//Transform temppos =
target.FindChild("Hair");
//temppos.position +=
Vector3.up*0.2f;
transform.LookAt(lookatpos);
//transform.Translate(transform.TransformDirecti on(Vector3.up)*2);
124 125 126 127 128
12
9
13
13
1
13
2
13
3
rotation = 0;
//Debug.Log(finalpos.y);
if (myprint )
{
myprint = false ;
}
} }
视角切换作为一个测试的版本卸载了一个测试脚本里面:
1 2 3 4 5 6 7 8 9 10
11
12
13
14
15
16
17
18
19
//debug_test.cs
using UnityEngine ;
using System.Collections ;
public class debug_test : MonoBehaviour {
PlayerCamera script ;
// Use this for initialization
void Start () {
script = Camera .main .GetComponent (); } // Update is called once per frame void Update () { if (Input .GetKeyUp (KeyCode .V )) script .bcatchview = !script .bcatchview ; } }
测试一下大概就是下面这样子:
这个相机脚本只实现了最基本的功能,想要获得最好的效果还需要加很多东西。我就不实现了哈。
一个ARPG 框架的搭建(二) 发表于 2014 年 1 月 27 日
接下来要实现的是角色控制脚本,角色控制有以下几点: ?
鼠标点击一处,如果是可达的则自动移动到此处,否则点击无效; ?
鼠标点击不放,角色向鼠标所指向的方向移动; ?
鼠标如果鼠标点击了NPC ,则向NPC 移动,如果NPC 在范围内则执行NPC 对话 ?
上述如果是敌人,则进入战斗状态 ? 出现UI 的时候鼠标点击移动无效
在写移动的时候遇到一个问题,如果使用Input.GetMouseButton(0)来获得鼠标左键点击按下情况则一定会触发 Input.GetMouseButtonDown(0),但是并不想要去触发Input.GetMouseButtonDown(0),于是用了个很弱 的方法来实现,这个方法还有点小问题,就是在开始按住移动的时候会出现动作缓慢的现象,不过在一般的跑步状态下看不太出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
private static int countframe = 0; //...... public static bool clicking () { if (Input .GetMouseButton (0)) { countframe ++; if (countframe > ;10) { return true ; } } else { countframe = 0; } return false ; } 代码略长,贴关键部分:
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 //......
if(clicking())
{
isautomove =false;
Vector3 vpos3 =
Camera.main.WorldToScreenPoint(transform.position);
Vector2 vpos2 =new Vector2
(vpos3.x,vpos3.y);
Vector2 input =new Vector2
(Input.mousePosition.x,Input.mousePosition.y);
if(Vector2.Distance(vpos2,input)
> 10.0f)
{
Vector2 normalied =
((vpos2 - input)).normalized;
targetDirection =new
Vector3(normalied.x,0.0f,normalied.y);
float y =
Camera.main.transform.rotation.eulerAngles.y;
targetDirection =
Quaternion.Euler(0f,y -180,0f)* targetDirection;
}
}
//...
canmove =true;
if(fighting&&fightingcount=25)
{
fighting =false;
fightingcount =0;
}
if(Input.GetMouseButtonDown(0))
{
RaycastHit hitfight;
Ray ray =
Camera.main.ScreenPointToRay(Input.mousePosition);
Physics.Raycast(ray,out
hitfight,100);
if(null!=hitfight.transform)
{
float distancePO =
Vector3.Distance(transform.position,hitfight.transform.
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
77
78
79
80
81
82
83
84
85
86
87
88 position);
//Debug.Log(distancePO);
if(distancePO().setActive();
Debug.Log("Hit NPC.");
}
break;
case"Enemy":
if(distancePO(),targetobject.GetComponent());
fighting =true;
//Debug.Log("Hit Enemy."+attack[0].name);
}
break;
case"Item":
if(distancePO<=3)
Debug.Log("Hit Item.");
break;
case"Event":
Debug.Log("Hit Event.");
break;
default:
Debug.Log("Hit Nothing."+hitfight.collider.gameObject.tag);
canmove =true;
break;
}
}
}
if(!PlayerCamera.bCatchView)
if(JFConst.TouchBegin()||isautomove)
{
isautomove =true;
Vector3 mousescreenpos =
Input.mousePosition;
Vector3 mouseworldpos =
Camera.main.ScreenToWorldPoint(mousescreenpos);
89
90
91
92
93
94
95
96
97
98
99 10 0 10 1 10 2 10 3 10 4 10 5 10 6 10 7 10 8
if(Input.GetMouseButtonDown(0))
{
Ray ray =
Camera.main.ScreenPointToRay(Input.mousePosition);
Physics.Raycast(ray, out hitt, 100);
if(null!= hitt.transform){
if(null!=testpos)
testpos.transform.position= hitt.point;
}
if( Physics.Linecast (transform.position, hitt.point+ hitoffset, out hit, mask.value))
{
string name =
hit.collider.gameObject.tag;
if(name != "WalkThrough")
isautomove = false;
//Debug.Log
(transform.position);
//Debug.Log(hitt.point);
//Debug.Log(hit.point);
testpos.transform.position= hit.point;
//Debug.Log (isautomove);
}
}
vpos3 =
Camera.main.WorldToScreenPoint(transform.position);
vpos2 =new Vector2 (vpos3.x,vpos3.y);
if(Input.GetMouseButtonDown(0))
input =new Vector2
(Input.mousePosition.x,Input.mousePosition.y);
Vector2 playerp =new
Vector2(transform.position.x,transform.position.z);
Vector2 hittp =new
Vector2(hitt.point.x,hitt.point.z);
normaliedo =(hittp -
playerp).normalized;
Debug.DrawLine(transform.position,hitt.point,Col or.red);
targetDirection =new
Vector3(normaliedo.x,0.0f,normaliedo.y);
var curdis =
Vector3.Distance(transform.position,hitt.point);
if(curdis<=distancefromtarget)
isautomove =false;
}