TheManAndMachine
实验性原创游戏Demo
游戏概况:
玩家通过向机器发送指令,使其进行移动、跳跃、攻击、发射钩锁等。通过各种障碍物和敌人区域。到达指定区域后过关。要注意的是,由于机器的四条腿是有血量的,当机械腿的血量为0时,会从机器身上断开。导致难以或不能移动。玩家通过特殊操作来避免和敌人正面接触。
机器概况:
机器是由四条机械臂,一个钩锁发射器,一个机关枪,等部件组成。机器的移动依靠机械臂上的活塞伸缩实现,除一些搞笑游戏外一般游戏开发基本上是不会用物理组件拼装一个对象,所以该游戏以搞笑元素和动作元素为铺垫,以解谜元素为主干。具体物理组件的拼装过程不做详细说明。
- 机器组件:
- 活塞
活塞是由2个铰链关节,2个弹簧关节,2个滑动关节组成
铰链关节用于连接杠杆或机器
滑动关节用于限制活塞杆的移动
弹簧关节用于控制活塞伸缩及保持一定张力。
- 活塞零件图
- 活塞属性
- 代码:
通过调整弹簧组件的Distance即可伸缩活塞,通过调整弹簧组件的张力,即可改变活塞力量。
#region 行为
private void Pull()//活塞拉回
{
spring2.distance -= motorSpeed;
if (spring2.distance < minDistance)
{
spring2.distance = minDistance;
}
}
private void Push()//活塞推出
{
spring2.distance += motorSpeed * maxDistance;
if (spring2.distance > maxDistance)
{
spring2.distance = maxDistance;
}
}
private void PowerPush()//活塞大推力推出
{
if (spring2.distance < canPowerPushDistance)//如果活塞距离小于规定距离则可以跳跃
{
spring2.distance = maxDistance;
spring2.frequency = powerPushPower;
Invoke("ResetHydraulic", resetTime);//一定时间后活塞弹簧初始化
}
else
{
spring2.distance = idleDistance;
}
}
private void ResetHydraulic()//用于将液压活塞回复到闲置状态
{
spring2.distance = idleDistance;
spring2.frequency = idleFrequency;
}
- 机械臂
一个机械臂由2个活塞组成,机械臂脚本聚合2个活塞组件;
- 机械的连接与管理结构
机器的机械臂管理系统聚合4个机械臂通过数据使其运动;
if (doJump)
{
AllDoublePull();
MakeSound();
if (jumpTimer.Update(Time.deltaTime))
{
doJump = false;
Jump();
_audio.PlayOneShot(jumpSound);
Invoke("AllReset", 2f);
}
}
else if (moveLeft)
{
WalkLeft();
MakeSound();
}
else if (moveRight)
{
WalkRight();
MakeSound();
}
if (needReset)
{
needReset = false;
AllReset();
_audio.PlayOneShot(resetSound);
}
if (doGetUpRight)
{
ReadyToGetUp(true);
if (jumpTimer.Update(Time.deltaTime))
{
doGetUpRight = false;
GetUpPowerPush(true);
_audio.PlayOneShot(getUpSound);
Invoke("AllReset", 0.3f);
}
}
else if (doGetUpLeft)
{
ReadyToGetUp(false);
if (jumpTimer.Update(Time.deltaTime))
{
doGetUpLeft = false;
GetUpPowerPush(false);
_audio.PlayOneShot(getUpSound);
Invoke("AllReset", 0.3f);
}
}
机械的移动
关于机械移动,就是控制活塞的伸进伸出控制手臂运动,使机器迈进
数据是由4个vector2数组构成
通过相同的index同步执行不同数据
private void WalkRight()
{
Timer();
if (wlakDataR1[step].z != 0)
{
armR1.WorkTo(wlakDataR1[step].x, wlakDataR1[step].y);
}
if (wlakDataR2[step].z != 0)
{
armR2.WorkTo(wlakDataR2[step].x, wlakDataR2[step].y);
}
if (wlakDataL1[step].z != 0)
{
armL1.WorkTo(wlakDataL1[step].x, wlakDataL1[step].y);
}
if (wlakDataL2[step].z != 0)
{
armL2.WorkTo(wlakDataL2[step].x, wlakDataL2[step].y);
}
}
发射锁钩功能
两条可以射出去的线:线射出去的一端遇到物体会用关节将其固定,另一端与机器连接
射出线后可以用滚轮进行拉扯
你永远只能伸缩最后射出去的那一根
private void CatcherAwake(Vector3 mousePos, GameObject catcher, Catcher catcherScript, Color lineColor)
{
//决定钩抓的方向
catcher.transform.eulerAngles = new Vector3(0, 0, transform.eulerAngles.z - eulerAngleOffset);
catcher.transform.position = firePosition.position;
//计算目标点
Vector2 dir = mousePos - firePosition.position;//计算出发射点到鼠标的方向
Ray2D rayToMouse = new Ray2D(firePosition.position, dir);//计算一条从发射点到鼠标位置的射线
//=================设置目标点和发射点,为重新发射做初始化//发射目标为该方向上绳索最大长度;
catcherScript.SetTarget(rayToMouse.GetPoint(catcherScript.maxDistance), firePosition, connecteBody, FakeCatcher);
//===================================================================================
catcherScript.lineColor = lineColor;
catcher.SetActive(true);//唤醒钩抓
FakeCatcher.SetActive(false);//隐藏假钩抓
}
攻击功能
- 防卫攻击,当机械各个组件收到伤害后,会向机械的控制系统告知攻击来源,并反击
- 玩家可以指定一个区域,让其攻击
if (curTarget != null)
{
Vector3 targetPosition = curTarget.transform.position;
transform.eulerAngles = LookAt.EulerAnglesUpdateWith(targetPosition, transform.position, 10f);
//================================================
HealthPointSystem hp = curTarget.GetComponent<HealthPointSystem>();
if (hp.hp > 0)
{
if (FireTimer())//满足攻击条件,攻击
{
GameObject b = Instantiate(bullet);
b.transform.position = firePoint.position;
b.transform.eulerAngles = LookAt.EulerAnglesUpdateWith(targetPosition, firePoint.position, 180f);
b.GetComponent<Bullet>().direction = targetPosition - firePoint.position;
_audio.PlayOneShot(MashineGunFire);
}
}
}
老头(Player):
- 老头动画:
- 老头移动
void FixedUpdate()
{
isOnGroundOrPlat = Physics2D.Linecast(checkPointA.position, checkPointB.position, layer)
|| Physics2D.Linecast(checkPointD.position, checkPointC.position, layer);
if (!GameManager.keyLock)
{
if (isCanMove)
{
MoveHorizontal();
}
if (doJump)
{
Jump();
}
}
else
{
animator.SetFloat("DirX", 0f);
}
animator.SetBool("OnGround", isOnGroundOrPlat);
}
- 老头的互动(开关,射线,机器)
- 使用那个东西:使用秘密武器使敌人暂时晕厥,秘密武器的构造系统完全不明,只是知道需要5秒钟时间充能完毕后才能使用
-
overLap.fillAmount = Mathf.Max(1 - curCdTime / cdTime, 0); 开关系统:老头的站在开关附近,可以开启地图上的开关
开关是解密的重要元素之一,需要被大量不同道具使用,如陷阱机关等等
考虑到有多种开关的存在和被玩家同一个脚本调用,开关应该从一个开关基类派生出多种开关
public class Switch : MonoBehaviour
{
public bool isTurnOn;
}
if (isSwitchOn)
{
_switch.isTurnOn = true;
if (that.localEulerAngles.z > 270f || that.localEulerAngles.z < 5)
{
that.Rotate(Vector3.forward, rotateSpeed * Time.fixedDeltaTime);
}
}
else
{
_switch.isTurnOn = false;
if (that.localEulerAngles.z > 285f || that.localEulerAngles.z < 20f)
{
that.Rotate(Vector3.forward, -rotateSpeed * Time.fixedDeltaTime);
}
}
- 机器指令脚本:老头拥有机器的控制权,机器的控制脚本聚合其所有组件(攻击,钩锁,四肢)
- 老头与机器:
- 老头与机器的互动关联
命令1:走:使用这个命令,使机器走向玩家面对的方向。
命令2:停:使机器停止移动
命令3:跟随:使机器跟随玩家
命令4:跳跃:机器跳跃
命令5:左右翻转,用于机械翻身使用
命令6 攻击:让机器主动攻击敌人
命令7:发射钩锁
if (!GameManager.keyLock)
{
if (Input.GetKeyDown(KeyCode.Alpha3))//老头向机器发出移动指令
{
GameManager.KeyLock(1.2f);
mashineControl.actionCode = 2;
MoveComond();
ShowComond(moveSprite);
}
else if (Input.GetKeyDown(KeyCode.Alpha1))//老头向机器发出待机指令
{
mashineControl.actionCode = 0;
animator.SetTrigger("StayHere");
GameManager.KeyLock(1.2f);
ShowComond(stopSprite);
}
else if (Input.GetKeyDown(KeyCode.Alpha2))//老头向机器发出跟随指令
{
GameManager.KeyLock(1.2f);
mashineControl.actionCode = 1;
transform.localScale = PlayerMove.FaceTo(mashineControl.transform.position.x - transform.position.x);
animator.SetTrigger("ComeHere");
ShowComond(followSprite);
}
else if (Input.GetKeyDown(KeyCode.C))
{
mashineControl.NeedGetUp(true);
}
else if (Input.GetKeyDown(KeyCode.Z))
{
mashineControl.NeedGetUp(false);
}
else if (Input.GetKeyDown(KeyCode.Alpha4))//跳跃;
{
mashineControl.Jump();
LeaveMashine();
Invoke("SetThatBoolFalse", 2.5f);
}
mashineControl.AttackAreaUpdate(Input.GetKeyDown(KeyCode.Q), Input.GetMouseButtonDown(0), Input.GetMouseButtonDown(1));//攻击
mashineControl.CatcherCast(Input.GetMouseButtonDown(0), Input.GetMouseButtonDown(1));//射出钩锁
}
- 老头坐机器:
老头跳到机器上后,用关节连接到机器上使其固定,玩家按跳跃键后脱离
Ps:
public void FixPlayer(Rigidbody2D playerBody)
{
if (!spring.enabled && isCanFix)
{
spring.enabled = true;
spring.connectedBody = playerBody;
}
}
敌人:
- 敌人行为:
-
hitRun:
攻击目标距离远就追,目标距离过近打一枪就跑,始终保持攻击目标与自身的距离private void HitRun(Transform target) { Vector2 shootDir = target.position - transform.position; Vector2 back = (Vector2)transform.position + Vector2.left * Mathf.Sign(shootDir.x); float distance = shootDir.magnitude; if (!isShooted)//先进行射击 { if (distance < (rifeGunRange.position - transform.position).magnitude)//如果力目标距离小于射程距离 { FaceTo(shootDir.x); animator.SetTrigger("Return"); animator.SetTrigger("RifleShoot"); if (!Physics2D.Linecast(transform.position, back, 1 << LayerMask.NameToLayer("Obstacle")))//负负得正 { isShooted = true; Invoke("IsRunTimeTrue", 1f);//等待射击动画播放完毕 } } else { MoveTo(target.position); } } else if (isRunTime)//射击一次完成后逃跑一段距离 { Vector2 runDir = (transform.position - target.position).normalized; if (distance < hitRunDistance) { Debug.DrawLine(transform.position, back); if (MoveTo(runDir + (Vector2)transform.position) || Physics2D.Linecast(transform.position, back, 1 << LayerMask.NameToLayer("Obstacle"))) { isRunTime = false; isShooted = false; } } else { isRunTime = false; isShooted = false; } } } -
Searching:
寻找玩家,如果遇到玩家就进入攻击模式private void Searching() { //Debug.DrawLine(transform.position, checkPointB.position, Color.yellow); RaycastHit2D[] hits = Physics2D.LinecastAll(transform.position, checkPointB.position); foreach (var hit in hits) { if (hit.collider.tag == "Obstacle") { return; } else if (hit.collider.tag == "Player") { stateMashine.ChangeState(new AttackState(hit.collider.gameObject.transform)); } } } -
Watching:放哨模式,如果发现异常就向目标走过去
private Vector2 Watching() { Debug.DrawLine(transform.position, checkPointA.position, Color.green); RaycastHit2D[] hits = Physics2D.LinecastAll(transform.position, checkPointA.position); foreach (var hit in hits) { if (hit.collider.tag == "Obstacle") { return transform.position; } else if (hit.collider.tag == "Player") { //配合动画控制器:射线发现玩家时>动画>setFindState>SearchState; animator.SetTrigger("FindSomething"); animator.SetBool("DoOtherThing", false); return hit.point; } } return transform.position; }
- 敌人状态机:
- 放哨模式:指定一个放哨点和放哨方向,并执行Watching方法,如果发现异常则进入搜寻模式
class StandState : State<SoldierAi>//站立模式
{
public StandState(Vector2 standPosition, Vector2 faceDir)
{
this.standPosition = standPosition;
this.faceDir = faceDir;
}
//用于指定一个站岗位置让其移动到该位置并开始放哨
private Vector2 standPosition;
private Vector2 faceDir;
private bool resetPosition;
private Vector2 wayReadyToGo;
private bool isFind;//优化动画使用,防止多次设置动画Trigger
public override void FixedUpdate(SoldierAi owner)
{
//让其移动到该位置并开始放哨
if (!resetPosition)
{
if (owner.MoveTo(standPosition))
{
owner.FaceTo(faceDir.x);
resetPosition = true;
}
}
//监视前方,如果发现异常则会向异常移动
if (!owner.isFindSomething)
{
if (!isFind)
{
wayReadyToGo = owner.Watching();
}
if (wayReadyToGo != (Vector2)owner.transform.position)
{
isFind = true;//注意该状态没有设置False选项,当状态机重新new出时才能回到false状态
}
}
else
{
owner.stateMashine.ChangeState(new SearchState(wayReadyToGo));
}
//一定时间会做其他小动作
DoOtherThings(owner);
}
float timer;
float toDoTime = 5f;
float doingTime = 2f;
bool isDoing;
private void DoOtherThings(SoldierAi owner)
{
timer += Time.deltaTime;
if (!isDoing && timer > toDoTime)
{
isDoing = true;
owner.animator.SetTrigger("DoOtherThing");
timer = 0;
}
else if (isDoing && timer > doingTime)
{
isDoing = false;
timer = 0;
}
}
}
-
搜索模式:向目标点行进并仔细搜索,如果发现玩家或机器,则进入攻击模式
class SearchState : State<SoldierAi>//搜寻模式 { public SearchState(Vector2 searchPoint) { this.searchPoint = searchPoint; } private Vector2 searchPoint; public override void Enter(SoldierAi owner) { owner.maxMoveSpeed *= 0.5f; } public override void Exit(SoldierAi owner) { owner.maxMoveSpeed *= 2f; } public override void FixedUpdate(SoldierAi owner) { if (owner.MoveTo(searchPoint))//逐个向异常点移动,返回是否移动到了; { owner.isFindSomething = false; owner.stateMashine.ChangeState(new StandState(owner.standPoint, owner.faceDir)); } //如果发现那个玩家则会攻击,如果发现机器则跑回去报告 owner.Searching(); } } -
攻击模式的基类:在基类中选择攻击模式
class AttackState : State<SoldierAi>//攻击模式 { protected Transform target; public AttackState(Transform target) { this.target = target; } public override void FixedUpdate(SoldierAi owner) { if (owner.rifleBulletNum != 0) { owner.stateMashine.ChangeState(new RifleGunAttackState(target)); } else { owner.stateMashine.ChangeState(new KnifeAttackState(target)); } } public override void Enter(SoldierAi owner) { owner.maxMoveSpeed *= 2f;//进入战斗的移动速度大幅提高 owner.attackTarget = target; } public override void Exit(SoldierAi owner) { owner.maxMoveSpeed *= 0.5f; } } -
来复枪攻击模式:主要调用hitRun方法进行攻击,直到没有子弹,没有子弹后使用小刀进行攻击
class RifleGunAttackState : AttackState//来复枪攻击模式 { public RifleGunAttackState(Transform target) : base(target) { } public override void Enter(SoldierAi owner) { base.Enter(owner); } public override void Exit(SoldierAi owner) { base.Exit(owner); } public override void FixedUpdate(SoldierAi owner) { if (owner.rifleBulletNum != 0) { owner.HitRun(target); } else { owner.stateMashine.ChangeState(new KnifeAttackState(target)); } } } -
匕首攻击模式:没有子弹后会进入近战攻击模式,非常残暴
```
class KnifeAttackState : AttackState//匕首攻击模式
{
public KnifeAttackState(Transform target) : base(target) { }
public override void FixedUpdate(SoldierAi owner)
{
float distance = (target.position - owner.transform.position).magnitude;
float knifeRange = (owner.knifeAttackRange.position - owner.transform.position).magnitude;
if (!owner.isKnifeAtt && distance < knifeRange)
{
owner.animator.SetBool("KnifeAttack", true);//动画控制脚本进行攻击
owner.isKnifeAtt = true;
}
else
{
owner.MoveTo(target.position);
}
}}
3. 敌人的出现
- 场景设置中默认存在的
- 由生成器刷新:
private void Spawn()
{
soldier = Instantiate(soldierPrefab);
soldier.transform.position = spawnPoint.position;
soldierNumber--;
if (isAttackState)
{
Invoke("AttackState", 1f);
}
else
{
Invoke("FaceTo", 0.5f);
}
}
private void FaceTo()
{
soldier.GetComponent
}
private void AttackState()
{
soldier.GetComponent
}
- 机械臂血量设定与伤害信息反馈
机械臂没有HP之后,会从身体部分分离
机械臂在受到伤害时,会将伤害的目标信息专递给控制脚本,再由攻击脚本进行反击

6. UI:
1. 血条:
2. 游戏结束
3. 游戏标题
4. 技能CD
7. 游戏管理器:
- UI控制
- 游戏胜负控制
- 游戏暂停
- 互相伤害:通过游戏管理器
public void MakeDamage(int damage, GameObject target)
{
HealthPointSystem hp = target.GetComponent
if (hp != null)
{
hp.hp -= damage;
if (hp.hp < 0)
{
hp.hp = 0;
}
}
}
```
扩展方向:
这个游戏的开发才刚刚开始
- 多个敌人种类,实现基类和派生类,或分解敌人AI使其各司其职,分工合作
- 实现多个敌人攻击方法
- 最初的版本中,机器是可以踢开,或踢飞一些障碍物,可以利用此对敌人造成伤害或弹射道具
- 道具拾取和使用
- 机器的维修和机械臂的再次安装
- 增加机器组件,玩家可以升级机器的一些装备