- 前言
- 資源下載
- 新增人物節點
- 運動狀態機
- 移動平臺
- 單向穿過
- 奇怪的Bug
- Area2D
- BodyEntered
- 死亡區域
- 全域性類
- 多執行緒安全
- TileMap處理
- TileMap分層
前言
這次來學習一下youtube的傳奇Unity博主,Breakeys的Godot新手教程。Breakeys是從15歲左右就開始用unity做遊戲並在youtube上面釋出影片了。他已經在youtube上面釋出了講解450個影片,然後他累了,3年前釋出了一個告別影片後離開了。因為前端時間的untiy收費事件,他又回來了。他並沒有明確的批評Unity,但是他說遊戲的未來應該是像Blender一樣的開源社群,而且Godot的完成度遠超他的想象。
基本的godot操作我們就不展開說明,我會對操作進行一些進階的程式碼替換。會跳過很多步驟,詳細的程式碼可以看我的github倉庫:https://github.com/Gclove2000/Brackeys-Godot-Beginner-Tutorial-In-Dotnet
資源下載
Brackeys' Platformer Bundle:https://brackeysgames.itch.io/brackeys-platformer-bundle
新增人物節點
這裡比較簡單,我就跳過了
運動狀態機
因為我之前寫過狀態機,我這裡就直接寫程式碼了。
using Godot;
using GodotGame.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GodotGame.SceneModels
{
public class PlayerSceneModel : ISceneModel
{
private PrintHelper printHelper;
private CharacterBody2D characterBody2D;
private Sprite2D sprite2D;
private AnimationPlayer animationPlayer;
private CollisionShape2D collisionShape2D;
public const int SPEED = 300;
public const int JUMP_VELOCITY = -400;
public enum AnimationEnum { Idel,Run,Roll,Hit,Death}
private AnimationEnum animationState = AnimationEnum.Idel;
public AnimationEnum AnimationState
{
get => animationState;
set
{
if(animationState != value)
{
printHelper?.Debug($"[{animationState}] => [{value}]");
animationState = value;
}
}
}
private bool isFlip = false;
public bool IsFlip
{
get => isFlip;
set
{
if(isFlip != value)
{
var postion = characterBody2D.Scale;
postion.X = -1;
characterBody2D.Scale = postion;
isFlip = value;
}
}
}
public PlayerSceneModel(PrintHelper printHelper)
{
this.printHelper = printHelper;
printHelper.SetTitle(nameof(PlayerSceneModel));
}
public override void Ready()
{
characterBody2D = Scene.GetNode<CharacterBody2D>("CharacterBody2D");
sprite2D = Scene.GetNode<Sprite2D>("CharacterBody2D/Sprite2D");
animationPlayer = Scene.GetNode<AnimationPlayer>("CharacterBody2D/AnimationPlayer");
collisionShape2D = Scene.GetNode<CollisionShape2D>("CharacterBody2D/CollisionShape2D");
printHelper.Debug("載入成功!");
}
public override void Process(double delta)
{
Move(delta);
Play();
SetAnimation();
}
private void SetAnimation()
{
if (!characterBody2D.IsOnFloor())
{
AnimationState = AnimationEnum.Roll;
}
switch (AnimationState)
{
case AnimationEnum.Idel:
if (!Mathf.IsZeroApprox(characterBody2D.Velocity.X))
{
AnimationState = AnimationEnum.Run;
}
break;
case AnimationEnum.Run:
if (Mathf.IsZeroApprox(characterBody2D.Velocity.X))
{
AnimationState = AnimationEnum.Idel;
}
break;
case AnimationEnum.Hit:
break;
case AnimationEnum.Death:
break;
case AnimationEnum.Roll:
if (characterBody2D.IsOnFloor())
{
AnimationState = AnimationEnum.Idel;
}
break;
}
if (!characterBody2D.IsOnFloor())
{
//printHelper.Debug("跳躍");
AnimationState = AnimationEnum.Roll;
}
}
private void Move(double delta)
{
var move = new Vector2(0,0);
move = characterBody2D.Velocity;
move.Y += (float)(MyGodotSetting.GRAVITY * delta);
if (MyGodotSetting.IsActionJustPressed(MyGodotSetting.InputMapEnum.Jump) && characterBody2D.IsOnFloor())
{
printHelper.Debug("跳躍");
move.Y = JUMP_VELOCITY;
}
var direction = Input.GetAxis(MyGodotSetting.InputMapEnum.Left.ToString(), MyGodotSetting.InputMapEnum.Right.ToString());
if(Mathf.IsZeroApprox(direction))
{
move.X = (float)Mathf.MoveToward(move.X, 0, delta*SPEED);
}
else
{
move.X = (float)Mathf.MoveToward(move.X, direction*SPEED, delta * SPEED);
IsFlip = direction < 0;
}
characterBody2D.Velocity = move;
characterBody2D.MoveAndSlide();
}
private void Play()
{
animationPlayer.Play(AnimationState.ToString());
}
}
}
移動平臺
StaticBody2D和他的子節點都適合用於製作不會移動的節點
單向穿過
如果我們想要一個單向的碰撞體,就可以開啟 One Way Collision 這個按鈕
奇怪的Bug
如果我們使用Node作為根節點來進行移動,就會導致整個碰撞層的錯誤,這裡我不知道為什麼
Area2D
Area2D一般用於製作簡單的無碰撞的物體
BodyEntered
之前我說過,在C# 中,不適用訊號而改用委託事件的方式,能在C# 內部解決的,就儘量不呼叫Godot的API。
using Godot;
using GodotGame.SceneScripts;
using GodotGame.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GodotGame.SceneModels
{
public class CoinSceneModel : ISceneModel
{
private PrintHelper printHelper;
private Area2D area2D;
private Sprite2D sprite2D;
private AnimationPlayer animationPlayer;
private CollisionShape2D collisionShape2D;
public CoinSceneModel(PrintHelper printHelper) {
this.printHelper = printHelper;
this.printHelper.SetTitle(nameof(CoinSceneModel));
}
public override void Process(double delta)
{
}
public override void Ready()
{
area2D = Scene.GetNode<Area2D>("Area2D");
sprite2D = Scene.GetNode<Sprite2D>("Area2D/Sprite2D");
animationPlayer = Scene.GetNode<AnimationPlayer>("Area2D/AnimationPlayer");
collisionShape2D = Scene.GetNode<CollisionShape2D>("Area2D/CollisionShape2D");
printHelper.Debug("載入完成");
area2D.BodyEntered += Area2D_BodyEntered;
}
private void Area2D_BodyEntered(Node2D body)
{
printHelper.Debug("有東西進入");
if (body is PlayerScene)
{
printHelper.Debug("玩家進入");
}
if(body.GetParent() is PlayerScene)
{
printHelper.Debug("父節點是玩家的進入");
}
}
}
}
這裡的碰撞檢測就用到了Godot的一個特性了,如果你使用了繼承的指令碼過載了節點,這樣相當於你新建了一個型別。比如Node2D節點掛載了一個繼承Node2D的 PlayerScene,這樣Godot就認為你是PlayerScene這個節點,這樣方便我們對各種碰撞事件的物件進行判斷
但是要注意的是,碰撞的物件只是Player的Area節點,所以還要去找他的父節點才可以找到對應的指令碼型別
當然,我們最好也設定一下物理層,這樣防止出現額外的碰撞事件。
死亡區域
全域性類
這裡我們就用全域性類來進行代替
using Godot;
using GodotGame.SceneScripts;
using GodotGame.Utils;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GodotGame.Modules
{
[GlobalClass]
public partial class DeathArea :Area2D
{
private PrintHelper printHelper;
public DeathArea() {
this.printHelper = Program.Services.GetService<PrintHelper>();
this.printHelper.SetTitle(nameof(DeathArea));
this.BodyEntered += DeathArea_BodyEntered;
}
private void DeathArea_BodyEntered(Node2D body)
{
printHelper.Debug("Anythiny enter!");
//如果玩家進入,則等待0.6秒後重新載入
if (body.GetParent() is PlayerScene)
{
printHelper.Debug("You Get Die");
Reload();
}
}
/// <summary>
/// 為了執行緒安全,我們只能這麼做
/// </summary>
/// <returns></returns>
private async Task Reload()
{
await Task.Delay(600);
GetTree().ReloadCurrentScene();
}
}
}
多執行緒安全
執行緒安全這裡就不展開說明了,我們目前暫時還沒接觸到大量的數學計算。