虽然游戏并没有多好玩,记录下我参与的部分的低级代码还是极好的(x
记录
写了个Recorder脚本,记录玩家的各种行为。
在我们这游戏里需要记录的行为只有左右移动和跳跃。
// Action Type - For recording and replaying public enum ActionType { IDLE, LEFT, RIGHT, JUMP, // ... }
检测行为(也可以耦合到玩家控制角色的脚本里,不过我还是觉得分开比较清楚)。移动的检测不检测按键大概是为了兼容手柄吧(虽然最终有些其它操作并没兼容手柄......时间太赶了
// Recording helper (detect input) private void DetectInput() { // Left & Right float horizon = Input.GetAxisRaw("Horizontal"); if (horizon == 1 && currentActionHorizon != ActionType.RIGHT) { SendActionHorizon(ActionType.RIGHT, Time.timeSinceLevelLoad); } else if (horizon == -1 && currentActionHorizon != ActionType.LEFT) { SendActionHorizon(ActionType.LEFT, Time.timeSinceLevelLoad); } else if (horizon == 0 && currentActionHorizon != ActionType.IDLE) { SendActionHorizon(ActionType.IDLE, Time.timeSinceLevelLoad); } // Jump if (Input.GetButtonDown("Jump") && currentActionVertical != ActionType.JUMP) { if (player.GetComponent<PlayerController>().isGround) SendActionVertical(ActionType.JUMP, Time.timeSinceLevelLoad); } if (Input.GetButtonUp("Jump") && currentActionVertical != ActionType.IDLE) { SendActionVertical(ActionType.IDLE, Time.timeSinceLevelLoad); } }
Sendxxx函数基本就是把东西加进List链表,然后更新当前状态。(把跳跃跟移动分开存是因为可以在空中控制移动...所以它们并不是互斥的
// Record current action public void SendActionHorizon(ActionType action, float time) { currentActionHorizon = action; Replayer.Records[GameManager.turn].Add(new Record(currentActionHorizon, currentActionVertical, time)); } public void SendActionVertical(ActionType action, float time) { currentActionVertical = action; Replayer.Records[GameManager.turn].Add(new Record(currentActionHorizon, currentActionVertical, time)); }
回放
在另一个Replayer脚本里,用一个List<List<Record>>的链表来记录。外层是第几轮的行为(因为这游戏可以死很多次然后都会回放的...),内层是行为。
public struct Record { public ActionType currentActionHorizon; public ActionType currentActionVertical; public float time; public Record(ActionType currentActionHorizon, ActionType currentActionVertical, float time) : this() { this.currentActionHorizon = currentActionHorizon; this.currentActionVertical = currentActionVertical; this.time = time; } }
回放的话当Time.timeSinceLevelLoaded>=time的时候执行相应动作,然后指针指向下一个待执行的动作就行。(我回放的人和Player是有一个共同父类的,就还蛮方便)
其它事情
当然挂这个脚本的物体得DontDestroyOnLoad。
还有个小坑(其实我觉得很不科学,怀疑是bug),就是当关卡重置(记录也应该重置)的时候,我们是直接Destroy了它,然后它的生成也是这个永不销毁的GameManager控制的,查找不到它就重新生成,听起来很文明但是不行。Destroy之后这个物体仍然可以被搜到,结果就是关卡重置之后还是搜得到所以根本没生成它,但它其实已经被删掉,编辑器里看不到了。(所以这个Find到底是什么鬼逻辑咯)最后改成了DestroyImmediate(但是发布的游戏版本是有这个bug的,当时太赶了没咋测试
public void Restart(){ // can't use Destroy(), as it will implement in the next frame... DestroyImmediate(RecorderInstance); Debug.Log("after destroy:" + GameObject.FindGameObjectWithTag("RecorderManager")); // ... }
游戏地址是:https://globalgamejam.org/2020/games/run-tear-run-0,玩起来一般般...并没有太多打广告的意思。如果感兴趣的话,AD左右移动,空格跳跃,Y原地暴毙,靠近尸体(就是回放的那些东西)按左键调方向可以扔出去(猜猜能砸啥东西下来)。祝大家身体健康游戏快乐。
暂无关于此日志的评论。