这磨人的小游戏项目也要接近尾声了,大概还差一两天的进度吧。最近在看佐佐木智广的《游戏剧本怎么写》,讲了很多Galgame的剧情、脚本设置,非常有意思的一本书,有时间我开个新坑来做一些读书笔记。下一个项目我也打算完成我以前能力不足未能写完的galgame框架,然后交给会剧本会画画的有缘人来操作(>A<),至于新课学习部分,明天离散数学书就来了,准备重点看预备知识巩固和图论部分,后面就做些题吧,暑假也就这样了。不多BB,继续撸项目
本日目标:
1.实现其他障碍物的触发检测
2.重写HeroControler,优化人物的动作
一.其他障碍物的设置
单单把障碍物放在那里让玩家去碰还是有点LOW了,我们加些脚本让障碍物有些变化,然后再加入昨天的动态道具创建,实现场景物体完全的动态生成。
(一)障碍物刺的制作
首先导入一个刺的模型,设置好比例尺,缩放到合适大小。我们来实现这样一个触发器常见效果:主人公走到刺的前面一段距离时,给刺发送信号,让其向玩家移动。还有不同方向的,也都来实现一遍。
这里的做法是使用AddComponent方法来动态添加脚本。先在刺的前面合适距离建一个空对象,作为一个触发装置,添加一个BoxCollider(IsTrigger打勾)。创建一个DynamicAddScript脚本,用于触发检测并给刺添加脚本,再创建一个控制物体移动的脚本ObjMovement。
DynamicAddScript脚本内容:
public class DynamicAddScripts : MonoBehaviour {
public GameObject GoNeedObject;
//定义添加脚本的对象
// Use this for initialization
void Start () {
}
private void OnTriggerEnter(Collider col)
{
if(GoNeedObject)//string.isnullorempty
{
GoNeedObject.AddComponent<MovingObj>();
}
}
// Update is called once per frame
void Update () {
}
}
ObjMovement脚本写一个简单的移动代码就能看到效果了
this.transform.Translate(Vector3.back * FloMovingSpeed,Space.World);
再来写一个垂直上下移动的刺,添加触发检测和脚本的方法一样,来写一下上下移动的逻辑。需要事先Debug找到合适的最高点和最低点,设为变量。再定义一个正向移动的标志,乘在vector3的后面(控制移动的方向)。
if(this.transform.position.y>FloYPositionmax)
{
UpMovingFlag = -1;
}
else if(this.transform.position.y<FloYPositionmin)
{
UpMovingFlag = 1;
}
this.transform.Translate(new Vector3(0,1,0) * MovingSpeed*UpMovingFlag
,Space.World);
然后把之前那个碰撞检测结束游戏的脚本绑在对应的刺上。将做好的物体设为预制体,方便之后的动态生成。注意脚本组件挂上相应的GameObject
(二)对障碍物树干的改动
来实现一个触发器控制树从空中落下来的效果,增加游戏难度。方法与之前的大同小异,只不过这回Add的是RigidBody组件。同样,写一个触发器上的脚本TriggerFalling:
public class DynamicTriggerFalling : MonoBehaviour {
public GameObject FallingObject;
// Use this for initialization
void Start () {
}
private void OnTriggerEnter(Collider col)
{
FallingObject.AddComponent<ObjectFalling>();
}
// Update is called once per frame
void Update () {
}
然后是添加刚体组件的脚本主要代码,注意初始化一下刚体的一些属性,用到了GetComponent方法:
private Rigidbody rb1;
// Use this for initialization
void Start () {
this.gameObject.AddComponent<Rigidbody>();
rb1 = this.gameObject.GetComponent<Rigidbody>();
rb1.mass = 5;
rb1.useGravity = true;
}
障碍物的设置就到这里,之后可以再增加一些特性,接下来来处理角色的动画。
二.角色动作的优化
目前角色的动画都放在HeroController中的,完全由Animation.play()来播放,这样子在加上跳跃,滑铲等动作后会出很大的问题,按下按键后动作会直接切换(不管播没播放结束),十分生硬。为了解决这个问题,需要重新写一下Controller中的内容,使用协程来控制人物动作。当然,这里也可以用动画状态机(animator)来实现,以后有时间专门来复习一下。
首先,在GlobalManager中新加入一个枚举变量,来指示人物的动作
public enum HeroActionType {None,Run,Jump,Bend,Fall}
//initialize
//7.28 action types
public static HeroActionType ActionType = HeroActionType.None;
然后打开HeroController脚本,把之前写的控制角色左右移动以及动画播放的代码全部注释掉,添加两个协程,一个用来接收用户输入,控制角色移动,一个用来控制对应的动作。这里跳跃动作用到了RigidBody的AddForce给物体添加一个冲力的方法来实现。
IEnumerator GetHeroActionInput()
{
yield return new WaitForEndOfFrame();
while(true)
{
yield return new WaitForSeconds(0.01f);
if(GlobalManager.GlobalState==GameState.Playing)
{
//Default setting:running
GlobalManager.ActionType = HeroActionType.Run;
//Turning
if(Input.GetKey(KeyCode.A))
{
this.transform.Rotate(Vector3.down * FloRotateSpeed);
}
else if (Input.GetKey(KeyCode.D))
{
this.transform.Rotate(Vector3.up * FloRotateSpeed);
}
//Jumping
else if(Input.GetKeyDown(KeyCode.Space))
{
HeroRB.AddForce(Vector3.up * HeroJumpPower, ForceMode.Impulse);
GlobalManager.ActionType = HeroActionType.Jump;
yield return new WaitForSeconds(AmimeJump.length);
}
else if(Input.GetKeyDown(KeyCode.S))
{
}
}
}
}
IEnumerator PlayHeroAnimation()
{
yield return new WaitForEndOfFrame();
while(true)
{
yield return new WaitForSeconds(0.01f);
if (GlobalManager.GlobalState==GameState.Playing)
{
switch(GlobalManager.ActionType)
{
case HeroActionType.None:
break;
case HeroActionType.Run:
anime.Play(AnimeRun.name);
yield return new WaitForSeconds(AnimeRun.length);
break;
case HeroActionType.Jump:
anime.Play(AmimeJump.name);
yield return new WaitForSeconds(AmimeJump.length);
break;
case HeroActionType.Bend:
break;
case HeroActionType.Fall:
break;
}
}
}
}
最好还是用switch来处理枚举变量。这里我们用到了AnimationClip,即剪辑得来的动画片段,我们把它的时间长度length来作为yield return的秒数,这样就不会出现一个动作还没有做完就强行开始进行下一个动作了。注意定义好animationClip,并且在脚本组件绑上对象。
GlobalManager和HeroController脚本变动很大,这里粘贴一下:
GlobalManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//enum description:
//none:no sounds
//halfvolume:halfvolume
//fullvolume:fullvolume
public enum ProjectVolume { none,FullVolume,HalfVolume,NoneVolume}
//enunm description:
//none:wait for a state
//preparing:counting down coroutine
//playing:normal game
//gameover:gameover
//pause:pause (game stop)
public enum GameState {none,Preparing,Playing,GameOver,Pause}
public enum HeroActionType {None,Run,Jump,Bend,Fall}
public class GlobalManager : MonoBehaviour {
//volume controller
public static ProjectVolume GlobalVol = ProjectVolume.FullVolume;
//game state controller
public static GameState GlobalState = GameState.none;
//count RedDiamonds
public static int RedDiamondCount=0;
//count Distance
public static int RunningDistance = 0;
//7.28 action types
public static HeroActionType ActionType = HeroActionType.None;
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Hero_Control_PC : MonoBehaviour {
// public GameObject GoHero;
public float FloRunningSpeed=1f;
public float FloRotateSpeed = 1f;
public float HeroJumpPower = 1f;
//7.28 Animation Clips
public AnimationClip AnimeRun;
public AnimationClip AmimeJump;
public AnimationClip AmimeBend;
public AnimationClip AmimeFall;
private Animation anime;
private Rigidbody HeroRB;
// Use this for initialization
void Start () {
anime = this.GetComponent<Animation>();
HeroRB = this.GetComponent<Rigidbody>();
//7.28 Get Key Input
StartCoroutine("GetHeroActionInput");
//play Hero's Animation
StartCoroutine("PlayHeroAnimation");
}
IEnumerator GetHeroActionInput()
{
yield return new WaitForEndOfFrame();
while(true)
{
yield return new WaitForSeconds(0.01f);
if(GlobalManager.GlobalState==GameState.Playing)
{
//Default setting:running
GlobalManager.ActionType = HeroActionType.Run;
//Turning
if(Input.GetKey(KeyCode.A))
{
this.transform.Rotate(Vector3.down * FloRotateSpeed);
}
else if (Input.GetKey(KeyCode.D))
{
this.transform.Rotate(Vector3.up * FloRotateSpeed);
}
//Jumping
else if(Input.GetKeyDown(KeyCode.Space))
{
HeroRB.AddForce(Vector3.up * HeroJumpPower, ForceMode.Impulse);
GlobalManager.ActionType = HeroActionType.Jump;
yield return new WaitForSeconds(AmimeJump.length);
}
else if(Input.GetKeyDown(KeyCode.S))
{
}
}
}
}
IEnumerator PlayHeroAnimation()
{
yield return new WaitForEndOfFrame();
while(true)
{
yield return new WaitForSeconds(0.01f);
if (GlobalManager.GlobalState==GameState.Playing)
{
switch(GlobalManager.ActionType)
{
case HeroActionType.None:
break;
case HeroActionType.Run:
anime.Play(AnimeRun.name);
yield return new WaitForSeconds(AnimeRun.length);
break;
case HeroActionType.Jump:
anime.Play(AmimeJump.name);
yield return new WaitForSeconds(AmimeJump.length);
break;
case HeroActionType.Bend:
break;
case HeroActionType.Fall:
break;
}
}
}
}
// Update is called once per frame
void Update () {
//7.24 latest updata
if(GlobalManager.GlobalState==GameState.Playing)
{
this.transform.Translate(Vector3.forward * FloRunningSpeed);
}
}
}