大部分游戏都鼓励玩家不断挑战更高的得分。虽然也有像角色扮演(RPG)这类更主张情节而不关注得分的游戏。但现在挑战更高分的游戏正变得越来越多。
这次我们介绍的是一个斩杀怪物的游戏。成功斩杀怪物后,怪物出现的数量会越来越多。玩家要尽可能地持续斩杀怪物,这样才能在游戏结束之前杀掉大量的怪物。反之,玩家一旦失守,怪物的数量就会减少。
当然,我们可以直接把倒下的怪物数量作为玩家的得分,但这里我们可以小小地改进一下,让游戏更有趣 。
假若武士在追赶怪物的过程中一直不攻击,最终将撞上怪物。在接近怪物的过程中,如果太近的话就会失手。玩家要在确保不撞上怪物的前提下尽可能地接近怪物并斩杀,这样游戏的技术难度就增加了。
这次我们设定了一个“靠近斩杀怪物将得到高分”的规则,但如果靠得太近又会导致武士撞上怪物而失手,这样游戏的就变得更加刺激了。
这种“高风险&高回报”的玩法很值得我们一试
在讨论如何判断攻击距离的远近之前,我们先说明一下攻击判定的原理。
玩家点击鼠标按键后,武士就会发起攻击。而攻击判定的计算就伴随着攻击行为的整个过程。
在格斗类游戏中,往往需要对玩家的拳脚和所使用的无器质性攻击判定。同样,也需要对被攻击者的每个关节部位进行伤害计算。正因为有了这样精确的碰撞检测,才能够实现诸如“蹲下躲开对方的回旋踢”,“爆头秒杀”等游戏特性。
不过我们这个游戏的碰撞检测并不需要细致到这种程度。由于怪物以很快的速度朝武士靠近,如果要严格按照刀的形状来进行检测,击中难度将大大增加,因此这里我们用一个大的球形来进行碰撞检测,并再播放攻击动作的时候将其放置在武士前面。
攻击的碰撞检测的执行实践,会比攻击动作的播放时间稍微长一些。如果提前按下鼠标按键,怪物九江进入上面所说的碰撞检测的球体中,这就意味着攻击成功。格斗游戏中常常有“预判”的说法。像这种敌人朝着玩家快速扑来的游戏,加入这种机制后会让游戏变得更容易上手。
通过提前进行碰撞检测和演唱检测的时间,就可以应对怪物快速运动的情况。那么,现在让我们回到主题,看看如何才能计算出"武士在多近的距离斩杀了怪物"。
首先可能会想“要计算武士在多近的距离斩杀了怪物,看看怪物和武士之间的距离不就行了吗?”。
如果这样想的话就错了!因为按照我们的设计思路,怪物会从外部撞向五十面前的碰撞检测用的球体,因此攻击成功时武士和怪物的距离必定等于该球体的半径。
下面来重新理解一下“靠近斩杀”这个过程。
攻击判定实在按键被按下的瞬间开始的。在这一瞬间,怪物和武士之间的距离可能痕迹,也可能很远。如果很远,怪物移动到碰撞检测的位置还需要一些时间。相反如果很近,碰撞检测很快就会进行。因此,我们只要计算出从执行碰撞检测(=摁下鼠标按键的瞬间)开始,到实际发生碰撞位置经过的时间,应该就可以计算出是在躲进的距离进行的斩杀了。
我们用PlayerControl.cs来管理攻击判定开始后的时间,下面列出代码
PlayerControl.attack_control方法:
private void attack_control()
{
if(!this.is_playable) {
return;
}
if(this.attack_timer > 0.0f) {
// 正在进行攻击判定
this.attack_timer -= Time.deltaTime;
// 攻击判定结束检测
if(this.attack_timer <= 0.0f) {
// 使碰撞器(攻击成功否)的碰撞判定无效
//
attack_collider.SetPowered(false);
}
} else {
this.attack_disable_timer -= Time.deltaTime;
if(this.attack_disable_timer > 0.0f) {
// 仍旧不可攻击
} else {
this.attack_disable_timer = 0.0f;
if(this.is_attack_input()) {
// 使碰撞器(攻击成功与否)的攻击判定有效
//
attack_collider.SetPowered(true);
this.attack_timer = PlayerControl.ATTACK_TIME;
this.attack_disable_timer = PlayerControl.ATTACK_DISABLE_TIME;
// 播放攻击动作
// 选择下一个播放的动作
//
// 因为要在决定“怪物”飞散的方向时知道“上一个攻击动作”
// 应该在播放前而非在播放后选择动作
//
switch(this.attack_motion) {
default:
case ATTACK_MOTION.RIGHT: this.attack_motion = ATTACK_MOTION.LEFT; break;
case ATTACK_MOTION.LEFT: this.attack_motion = ATTACK_MOTION.RIGHT; break;
}
switch(this.attack_motion) {
default:
case ATTACK_MOTION.RIGHT: this.animator.SetTrigger("attack_r"); break;
case ATTACK_MOTION.LEFT: this.animator.SetTrigger("attack_l"); break;
}
this.attack_voice_audio.PlayOneShot(this.AttackSound[this.attack_sound_index]);
this.attack_sound_index = (this.attack_sound_index + 1)%this.AttackSound.Length;
this.sword_audio.PlayOneShot(this.SwordSound);
}
}
}
}
其中,attack_timer是用来记录攻击判定持续时间的计时器。在按键被按下的瞬间会用一个特定的值对它进行初始化,随着时间减少,当它的值变为0时,则意味着攻击判定执行结束。
attack_timer表示的知识攻击判定的“剩余时间”,为了获得用于判断“在多近的距离斩杀”的“经过实践”,我们还需要一个GetAttackTimer方法:
// 算出从开始攻击(点击鼠标按键起)经过的时间
public float GetAttackTimer()
{
return(PlayerControl.ATTACK_TIME - this.attack_timer);
}
SceneControl.cs的AddDefeatNum方法在武士攻击命中怪物时被执行
SceneControl.AddDefeatNum方法:
public void AddDefeatNum(int num)
{
this.oni_group_defeat_num++;
this.oni_group_complite++;
this.result.oni_defeat_num += num;
// 通过点击按钮后结果的时间来决定评价的好坏
// (点击后到攻击命中的时间短=在非常精确的时刻斩杀了怪物)
this.attack_time = this.player.GetComponent().GetAttackTimer();
if(this.evaluation == EVALUATION.MISS) {
// 失败(慢吞吞地运行中)后,只会出现OKAY
this.evaluation = EVALUATION.OKAY;
} else {
if(this.attack_time < ATTACK_TIME_GREAT) {
this.evaluation = EVALUATION.GREAT;
} else if(this.attack_time < ATTACK_TIME_GOOD) {
this.evaluation = EVALUATION.GOOD;
} else {
this.evaluation = EVALUATION.OKAY;
}
}
if(SceneControl.IS_AUTO_ATTACK) {
this.evaluation = this.evaluation_auto_attack;
}
this.result.eval_count[(int)this.evaluation] += num;
// 计算得分
float[] score_list = { this.eval_rate_okay, this.eval_rate_good, this.eval_rate_great, 0 };
this.result.score_max += num * this.eval_rate_great;
this.result.score += num * score_list[(int)this.evaluation];
this.result_control.addOniDefeatScore(num);
this.result_control.addEvaluationScore((int)this.evaluation);
}
最后,设定用于度量经过时间和得分高低关系的ATTACK_TIME_GREAT和ATTACK_TIME_GOOD的值。如果经过时间小于ATTACK_TIME_GREAT并在足够近的距离内斩杀了怪物则判定为GREAT,若时间比ATTACK_TIME_GOOD短则判定为GOOD,除此之外在原距离斩杀怪物的情况判定为OKEY。每次攻击后都记录下判定的结果,游戏结束后再通过这些结果来决定玩家的总成绩
通过改变用于衡量得分高低的经过时间的阈值(ATTACK_TIME_GREAT和ATTACK_TIME_GOOD),可以调整游戏高分的难度。这些数值是决定游戏平衡性的重要因素,我们要对照效果调整出最合适的数值。