首页 > 其他分享 >Godot Breakeys Godot Beginner Tutorial 游戏开发笔记

Godot Breakeys Godot Beginner Tutorial 游戏开发笔记

时间:2024-05-20 14:09:52浏览次数:31  
标签:Godot Beginner AnimationEnum printHelper System private using public Breakeys

目录

前言

这次来学习一下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();

        }
    }
}

多线程安全

线程安全这里就不展开说明了,我们目前暂时还没接触到大量的数学计算。

TileMap处理

TileMap分层

标签:Godot,Beginner,AnimationEnum,printHelper,System,private,using,public,Breakeys
From: https://www.cnblogs.com/gclove2000/p/18173914

相关文章

  • AtCoder Beginner Contest 354
    A-ExponentialPlant(abc354A)题目大意某星球上的植物,初始高\(0\),然后每天依次增长\(1,2,4,8,...\),问哪天就高过身高为\(h\)的高桥。解题思路因为是指数级别长高,枚举一下天数即可,由于\(h\leq10^9\),因此天数不会超过\(32\)天。神奇的代码#include<bits/stdc++.h>u......
  • AtCoder Beginner Contest 351 E - Jump Distance Sum
    题目链接Hint1:只能斜着走,容易想到黑白棋盘,\((x+y)\%2==0\)位于一个团,\((x+y)\%2==1\)位于另一个团,分别求和。Hint2:\(dist=max(|x1-x2|,|y1-y2|)\),这是切比雪夫距离,将坐标系倾斜\(45^{\circ}\),改为曼哈顿距离\(dist=|x1-x2|+|y1-y2|\),即\(X=x+y,Y=x-y\),这样就可以将横纵坐标......
  • AtCoder Beginner Contest 352 E - Clique Connect
    题目链接不需要将所有边都建立出来,根据\(Kruskal\)最小生成树的贪心策略,相同权值的边不需要形成团,形成一个链就行,对结果没有影响。时间复杂度\(O(mlogm)[m=\sum_{i=1}^{n}k_{i}]\)。#pragmaGCCoptimize(2)#pragmaGCCoptimize(3)#include<bits/stdc++.h>//#defineint......
  • AtCoder Beginner Contest 353
    AtCoderBeginnerContest353abc353_c题意:定义\(F(x,y)\)为\((x+y)mod10^8\)的值,求\(\displaystyle\sum_{i=1}^{N-1}\sum_{j=i+1}^Nf(A_i,A_j).\)思路:对于\(\displaystyle\sum_{i=1}^{N-1}\sum_{j=i+1}^N\f(A_i,A_j).\)来说,每个\(A_i\)的次数都是\(n-1\)次,所以如果没有\(m......
  • Atcoder Beginner Contest 353
    AtcoderBeginnerContest353A问题陈述有\(N\)幢楼房排列成一排。左起的\(i\)th楼高\(H_i\)。请判断是否有比左起第一座高的建筑物。如果存在这样的建筑物,请找出从左边起最左边的建筑物的位置。思路签到题。代码#include<bits/stdc++.h>usingnamespacestd;int......
  • AtCoder Beginner Contest 353
    AtCoderBeginnerContest353A-Buildings求第一个\(h_i\)大于\(h_1\)的位置。模拟。点击查看代码#include<bits/stdc++.h>#defineintlonglongusingnamespacestd;intn,h[103];signedmain(){ cin>>n;cin>>h[1]; for(inti=2;i<=n;i++){ cin&......
  • 如何编译Godot(Godot & Godot with C#)
    要在Windows下编译Godot,需要以下环境:VisualStudioCommunity:使用最新版本。MinGW-w64:可以替代VisualStudio。请务必将其安装/配置为使用posix线程模型。使用MinGW编译主分支时,需要GCC9或更高版本。Python3.6+:确保在安装程序中启用将Python添加到环境变量......
  • AtCoder Beginner Contest 353
    A-Buildings(abc353A)题目大意给定\(n\)个数字,输出第一个大于第一个数的下标。解题思路依次与第一个数比较,大于则输出。神奇的代码n=input()a=list(map(int,input().split()))b=[i>a[0]foriina]ifTruenotinb:print(-1)else:print(b.ind......
  • AtCoder Beginner Contest 318 Ex Count Strong Test Cases
    洛谷传送门AtCoder传送门首先做一些初步的观察:A和B的解法是对称的,所以A对的方案数等于B对的方案数。同时若A和B同时对则每个置换环环长为\(1\),方案数为\(n!\)。所以,若设A对的方案数为\(x\),那么答案为\(n!^2-(x-n!)-(x-n!)-n!=n!^2+n!-x\)。......
  • AtCoder Beginner Contest 352
    AtCoderBeginnerContest352A-AtCoderLine给\(N,X,Y,Z\)判断是否\(\min(X,Y)\leZ\le\max(X,Y)\)。模拟。点击查看代码#include<bits/stdc++.h>#defineintlonglongusingnamespacestd;intn,x,y,z;signedmain(){ cin>>n>>x>>y>......