【Godot】2.0 3D火箭关卡游戏

初始化

增加环境和太阳光,模型和照相机。


image.png
image.png
image.png
image.png

实现跳跃

image.png

Player 节点增加脚本Player.cs

using Godot;
using System;

public partial class Player : Node3D
{
    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
        // 跳跃
        if (Input.IsActionJustPressed("跳跃"))
        {
            this.Position = new Vector3(Position.X, Position.Y + 2f, Position.Z);
        }
    }
}

缓慢向上跳跃

using Godot;
using System;

public partial class Player : Node3D
{
    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
        // 跳跃
        if (Input.IsActionPressed("跳跃"))
        {
            this.Position = new Vector3(Position.X, Position.Y + (float)delta , Position.Z);
        }
    }
}

旋转

using Godot;
using System;

public partial class Player : Node3D
{
    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
        // 跳跃
        if (Input.IsActionPressed("跳跃"))
        {
            this.Position = new Vector3(Position.X, Position.Y + (float)delta, Position.Z);
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("左"))
        {
            this.RotateZ(-(float)delta);
        }
        // 逆时针旋转形状
        if (Input.IsActionPressed("右"))
        {
            this.RotateZ((float)delta);
        }
    }
}

游戏设计

image.png

主要由发射台、火箭、着陆台、地面、和其他障碍物组成。

等级场景

拆分第一个场景:等级场景。

这里用到一个基础组件CSGBox3D-构造性立体几何,用于创建各种3D模型。

image.png

image.png

地面基本设置:
image.png

创建一个标准物理材质,这会让地面更轻一些。
image.png

3D物体的颜色修改,在这里叫反照率(Albedo):
我们这里设置颜色为#66452d
image.png

image.png

创建发射台、着陆台

同上, 发射台、着陆台大小(2,0.4,2),都开启碰撞, 发射台位置(-7.5,0.2,0)颜色#56a9c9,着陆台位置(7.5,0.2,0)颜色#16ba3d。

image.png

环境

增加环境3要素:环境、太阳、3D相机。


image.png

拖动3D相机的z轴到合适的位置,可以点开预览图查看实际效果(最终位置为0,0,11)。


image.png

image.png

但这不是我这个游戏设计的最终效果,最终位置为(0,4,8.4
),旋转设置为(-2.2,0,0)


image.png

游戏运行效果如下:


image.png

火箭(玩家)

将玩家场景实例化:


image.png

image.png

这里我们不需要火箭实现环境3要素,点进去删除:


image.png
image.png

将火箭放在发射台的子节点,位置归0,就会发现火箭的位置会基于父节点初始化。

image.png

火箭向Y轴调整在底座上即可(基座高0.2m+自身一半的高度1m)。


image.png

然后将火箭从发射台子节点移出,因为我们并不需要将其设置为发射台的子节点,挪出后坐标数值改变,但模型位置不变。


image.png

修改一下脚本

......

        // 逆时针旋转形状
        if (Input.IsActionPressed("左"))
        {
            this.RotateZ((float)delta);
        }
        // 逆时针旋转形状
        if (Input.IsActionPressed("右"))
        {
            this.RotateZ(-(float)delta);
        }
......

运行,火箭我们还能继续操控。


image.png

增加火箭刚体

进入玩家场景,更改根节点类型为刚体RigidBody3D,并增加碰撞体。
锁定火箭仅支持向右和向上运动,不让沿任何轴旋转,也就是锁定Z轴移动和X轴、Y轴旋转。


image.png

image.png

提高阻尼,让物理更现实。


image.png

修改Player.cs脚本。
using Godot;
using System;

public partial class Player : RigidBody3D
{
    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
        // 跳跃
        if (Input.IsActionPressed("跳跃"))
        {
            // this.Position = new Vector3(Position.X, Position.Y + (float)delta, Position.Z);
            // 施加中心力,向上
            this.ApplyCentralForce(Vector3.Up * (float)delta * 1000.0f);
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("左"))
        {
            this.RotateZ((float)delta);
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("右"))
        {
            this.RotateZ(-(float)delta);
        }
    }
}

运行,可以发现火箭已经可以起飞了!


image.png

增加旋转

using Godot;
using System;

public partial class Player : RigidBody3D
{
    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
        // 跳跃
        if (Input.IsActionPressed("跳跃"))
        {
            // this.Position = new Vector3(Position.X, Position.Y + (float)delta, Position.Z);
            // 施加中心力,向上
            this.ApplyCentralForce(Vector3.Up * (float)delta * 1000.0f);
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("左"))
        {
            // this.RotateZ((float)delta);
            this.ApplyTorque(Vector3.Back * (float)delta * 100.0f);
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("右"))
        {
            // this.RotateZ(-(float)delta);
            this.ApplyTorque(Vector3.Forward * (float)delta * 100.0f);
        }
    }
}

增加 空间变化的位置。
用到Basis.Y,Basis输出3*3的矩阵。
https://www.bookstack.cn/read/godot-4.2-zh/15622d61f41d4975.md

image.png

修改脚本

using Godot;
using System;

public partial class Player : RigidBody3D
{
    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
        // 跳跃
        if (Input.IsActionPressed("跳跃"))
        {
            // this.Position = new Vector3(Position.X, Position.Y + (float)delta, Position.Z);
            // 施加中心力,向上
            this.ApplyCentralForce(Basis.Y * (float)delta * 1000.0f);
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("左"))
        {
            // this.RotateZ((float)delta);
            this.ApplyTorque(Vector3.Back * (float)delta * 100.0f);
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("右"))
        {
            // this.RotateZ(-(float)delta);
            this.ApplyTorque(Vector3.Forward * (float)delta * 100.0f);
        }
    }
}

运行会发现,已经可以像火箭一样发射了。

配置碰撞事件(信号)

Player刚体上有一个属性,打开,并将最大接触点数为10。


image.png
image.png

修改Plyaer.cs脚本。

using Godot;
using System;

public partial class Player : RigidBody3D
{
    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
        // 跳跃
        if (Input.IsActionPressed("跳跃"))
        {
            // this.Position = new Vector3(Position.X, Position.Y + (float)delta, Position.Z);
            // 施加中心力,向上
            this.ApplyCentralForce(Basis.Y * (float)delta * 1000.0f);
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("左"))
        {
            // this.RotateZ((float)delta);
            this.ApplyTorque(Vector3.Back * (float)delta * 100.0f);
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("右"))
        {
            // this.RotateZ(-(float)delta);
            this.ApplyTorque(Vector3.Forward * (float)delta * 100.0f);
        }
    }

    public void onBodyEnterd(Node body)
    {
        if (body.Name == "LandingPad")
        {
            GD.Print("闯关成功!");
        }
    }
}

绑定信号。


image.png

游戏可以初步判定是赢了。

优化

利用分组实现判定,Level场景,LandingPad增加分组Goal。

image.png

  public void onBodyEnterd(Node body)
    {
        if (body.GetGroups().Contains("Goal"))
        {
            GD.Print("闯关成功!");
        }
    }

增加组就增加失败:

......
    public void onBodyEnterd(Node body)
    {
        if (body.GetGroups().Contains("Goal"))
        {
            GD.Print("闯关成功!");
        }
        if (body.GetGroups().Contains("Hazard"))
        {
            GD.Print("挑战失败!");
        }
    }
......

将参数暴露出去。

using Godot;
using System;

public partial class Player : RigidBody3D
{
    // 推力 "750.0,3000.0,100.0,or_greater,or_less"
    /// ## string(推力值)
    [Export(PropertyHint.Range, "750.0,3000.0")]
    public float THRUST = 1000.0f;

    // 倾斜力
    [Export(PropertyHint.Range, "20.0,1000.0")]
    public float TORQUE_THRUST = 100.0f;

    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
        // 跳跃
        if (Input.IsActionPressed("跳跃"))
        {
            // this.Position = new Vector3(Position.X, Position.Y + (float)delta, Position.Z);
            // 施加中心力,向上
            this.ApplyCentralForce(Basis.Y * (float)delta * THRUST);
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("左"))
        {
            // this.RotateZ((float)delta);
            this.ApplyTorque(Vector3.Back * (float)delta * TORQUE_THRUST);
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("右"))
        {
            // this.RotateZ(-(float)delta);
            this.ApplyTorque(Vector3.Forward * (float)delta * TORQUE_THRUST);
        }
    }

    public void onBodyEnterd(Node body)
    {
        if (body.GetGroups().Contains("Goal"))
        {
            GD.Print("闯关成功!");
        }

        if (body.GetGroups().Contains("Hazard"))
        {
            GD.Print("挑战失败!");
        }
    }
}
image.png

完善游戏逻辑

using Godot;
using System;

public partial class Player : RigidBody3D
{
    // 推力 "750.0,3000.0,100.0,or_greater,or_less"
    /// ## string(推力值)
    [Export(PropertyHint.Range, "750.0,3000.0")]
    public float THRUST = 1000.0f;

    // 倾斜力
    [Export(PropertyHint.Range, "20.0,1000.0")]
    public float TORQUE_THRUST = 100.0f;

    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
        // 跳跃
        if (Input.IsActionPressed("跳跃"))
        {
            // this.Position = new Vector3(Position.X, Position.Y + (float)delta, Position.Z);
            // 施加中心力,向上
            this.ApplyCentralForce(Basis.Y * (float)delta * THRUST);
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("左"))
        {
            // this.RotateZ((float)delta);
            this.ApplyTorque(Vector3.Back * (float)delta * TORQUE_THRUST);
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("右"))
        {
            // this.RotateZ(-(float)delta);
            this.ApplyTorque(Vector3.Forward * (float)delta * TORQUE_THRUST);
        }
    }

    public void onBodyEnterd(Node body)
    {
        if (body.GetGroups().Contains("Goal"))
        {
            completeLevel();
        }

        if (body.GetGroups().Contains("Hazard"))
        {
            crashSequence();
        }
    }

    public void crashSequence()
    {
        GD.Print("大爆炸!");
        this.GetTree().ReloadCurrentScene();
    }
    public void completeLevel()
    {
        GD.Print("挑战成功!");
        this.GetTree().Quit();
    }
}

继续优化,增加关卡,关卡障碍绑定到LandingPad节点。
LandingPad节点创建脚本LandingPad.cs

using Godot;
using System;

public partial class LandingPad : CsgBox3D
{
    
    [Export(PropertyHint.File,"*.tscn")]
    public string FilePath { get; set; }
    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
    }
}

修改Palyer.cs

using Godot;
using System;

public partial class Player : RigidBody3D
{
    // 推力 "750.0,3000.0,100.0,or_greater,or_less"
    ///## string(推力值)
    [Export(PropertyHint.Range, "750.0,3000.0")]
    public float THRUST = 1000.0f;

    // 倾斜力
    [Export(PropertyHint.Range, "20.0,1000.0")]
    public float TORQUE_THRUST = 100.0f;

    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
        // 跳跃
        if (Input.IsActionPressed("跳跃"))
        {
            // this.Position = new Vector3(Position.X, Position.Y + (float)delta, Position.Z);
            // 施加中心力,向上
            this.ApplyCentralForce(Basis.Y * (float)delta * THRUST);
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("左"))
        {
            // this.RotateZ((float)delta);
            this.ApplyTorque(Vector3.Back * (float)delta * TORQUE_THRUST);
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("右"))
        {
            // this.RotateZ(-(float)delta);
            this.ApplyTorque(Vector3.Forward * (float)delta * TORQUE_THRUST);
        }
    }

    public void onBodyEnterd(Node body)
    {
        if (body.GetGroups().Contains("Goal"))
        {
            completeLevel(body.Get("FilePath").AsString());
        }

        if (body.GetGroups().Contains("Hazard"))
        {
            crashSequence();
        }
    }

    public void crashSequence()
    {
        GD.Print("大爆炸!");
        this.GetTree().ReloadCurrentScene();
    }

    public void completeLevel(String nextLevelFile)
    {
        GD.Print(nextLevelFile + ",挑战成功!");
        this.GetTree().ChangeSceneToFile(nextLevelFile);
    }
}

赋值一个场景。


image.png

这样就实现了关卡成功切换下一个关卡。

下一个关卡

Ctrl+D复制场景level.tscn,为level_2.tscn;
打开level_2.tscn,复制粘贴Floor,调整参数,位置参数(0,1,0)

image.png

然后修改filePath,即可实现关卡通关,切换下一个关卡场景。


image.png

操作的过渡和缓动

调整

    public void crashSequence()
    {
        GD.Print("大爆炸!");
        Tween tween = this.GetTree().CreateTween();;
        tween.TweenInterval(1.0);
        tween.TweenCallback(Callable.From(this.GetTree().ReloadCurrentScene));
    }

场景会过1秒再加载。

失败或成功后,禁用玩家操作。

using Godot;
using System;

public partial class Player : RigidBody3D
{
    // 推力 "750.0,3000.0,100.0,or_greater,or_less"
    ///## string(推力值)
    [Export(PropertyHint.Range, "750.0,3000.0")]
    public float THRUST = 1000.0f;

    // 倾斜力
    [Export(PropertyHint.Range, "20.0,1000.0")]
    public float TORQUE_THRUST = 100.0f;

    // 是否过渡中
    private Boolean isTransitioning = false;

    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
        // 跳跃
        if (Input.IsActionPressed("跳跃"))
        {
            // this.Position = new Vector3(Position.X, Position.Y + (float)delta, Position.Z);
            // 施加中心力,向上
            this.ApplyCentralForce(Basis.Y * (float)delta * THRUST);
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("左"))
        {
            // this.RotateZ((float)delta);
            this.ApplyTorque(Vector3.Back * (float)delta * TORQUE_THRUST);
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("右"))
        {
            // this.RotateZ(-(float)delta);
            this.ApplyTorque(Vector3.Forward * (float)delta * TORQUE_THRUST);
        }
    }

    public void onBodyEnterd(Node body)
    {
        if (!isTransitioning)
        {
            if (body.GetGroups().Contains("Goal"))
            {
                completeLevel(body.Get("FilePath").AsString());
            }

            if (body.GetGroups().Contains("Hazard"))
            {
                crashSequence();
            }
        }
    
    }

    public void crashSequence()
    {
        GD.Print("大爆炸!");
        this.SetProcess(false);
        isTransitioning = true;
        Tween tween = this.GetTree().CreateTween();
        ;
        tween.TweenInterval(1.0);
        tween.TweenCallback(Callable.From(()=>this.GetTree().ReloadCurrentScene()));
    }

    public void completeLevel(String nextLevelFile)
    {
        GD.Print(nextLevelFile + ",挑战成功!");
        isTransitioning = true;
        Tween tween = this.GetTree().CreateTween();
        tween.TweenInterval(1.0);
        tween.TweenCallback(Callable.From(()=>this.GetTree().ChangeSceneToFile(nextLevelFile)));
    }
}

动的障碍物

image.png

image.png

快捷创建碰撞体:


image.png

image.png
image.png

给个颜色。


image.png

可能别的地方要用到这样的移动刚体,将其创建为场景。


image.png

这个分支只有一个立方体,新增根节点脚本MovingHazard.cs。
image.png
using Godot;
using System;

public partial class MovingHazard : AnimatableBody3D
{
    // 目的地
    [Export]
    public Vector3 destination;
    // 持续时间
    [Export]
    public float duration=1.0f;
    public override void _Ready()
    {
        Tween tween = this.GetTree().CreateTween();
        tween.TweenProperty(this,"global_position",GlobalPosition+destination,duration);
    }

    public override void _Process(double delta)
    {
    }
}

这里用到TweenProperty( GodotObject object, NodePath property, Variant finalVal, double duration)函数。

object 需要缓动的对象体,这里用自身。
property,要缓动改变的属性(比例、位置等)
finalVal 最终·要变化过去的值,
duration 持续时间

设置循环执行和正弦速率:

    public override void _Ready()
    {
        Tween tween = this.GetTree().CreateTween();
        // 循环执行
        tween.SetLoops();
        // 正弦运动,运动效果更加圆润。
        tween.SetTrans(Tween.TransitionType.Sine);
        tween.TweenProperty(this, "global_position", GlobalPosition + destination, duration);
        tween.TweenProperty(this, "global_position", GlobalPosition, duration);
    }

增加碰撞。


image.png

效果完成!

音效

这里只需要一个全局的就够用了。


image.png

加载音频,配置音量:


image.png

修改脚本

using Godot;
using System;

public partial class Player : RigidBody3D
{
    private AudioStreamPlayer explosionAudio;
    private AudioStreamPlayer successAudio;

    // 推力 "750.0,3000.0,100.0,or_greater,or_less"
    ///## string(推力值)
    [Export(PropertyHint.Range, "750.0,3000.0")]
    public float THRUST = 1000.0f;

    // 倾斜力
    [Export(PropertyHint.Range, "20.0,1000.0")]
    public float TORQUE_THRUST = 100.0f;

    // 是否过渡中
    private Boolean isTransitioning = false;

    public override void _Ready()
    {
        explosionAudio =
            this.GetNode<AudioStreamPlayer>("ExplosionAudio");
        successAudio =
            this.GetNode<AudioStreamPlayer>("SuccessAudio");
    }

    public override void _Process(double delta)
    {
        // 跳跃
        if (Input.IsActionPressed("跳跃"))
        {
            // this.Position = new Vector3(Position.X, Position.Y + (float)delta, Position.Z);
            // 施加中心力,向上
            this.ApplyCentralForce(Basis.Y * (float)delta * THRUST);
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("左"))
        {
            // this.RotateZ((float)delta);
            this.ApplyTorque(Vector3.Back * (float)delta * TORQUE_THRUST);
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("右"))
        {
            // this.RotateZ(-(float)delta);
            this.ApplyTorque(Vector3.Forward * (float)delta * TORQUE_THRUST);
        }
    }

    public void onBodyEnterd(Node body)
    {
        if (!isTransitioning)
        {
            if (body.GetGroups().Contains("Goal"))
            {
                completeLevel(body.Get("FilePath").AsString());
            }

            if (body.GetGroups().Contains("Hazard"))
            {
                crashSequence();
            }
        }
    }

    public void crashSequence()
    {
        isTransitioning = true;
        GD.Print("大爆炸!");
        this.SetProcess(false);
        explosionAudio.Play();
        Tween tween = this.GetTree().CreateTween();
        ;
        tween.TweenInterval(2.5);
        tween.TweenCallback(Callable.From(() => this.GetTree().ReloadCurrentScene()));
    }

    public void completeLevel(String nextLevelFile)
    {
        isTransitioning = true;
        GD.Print(nextLevelFile + ",挑战成功!");
        successAudio.Play();
        Tween tween = this.GetTree().CreateTween();
        tween.TweenInterval(1.5);
        tween.TweenCallback(Callable.From(() => this.GetTree().ChangeSceneToFile(nextLevelFile)));
    }
}

火箭运动音效

image.png
image.png

image.png

脚本改造

using Godot;
using System;

public partial class Player : RigidBody3D
{
......
    private AudioStreamPlayer3D rocketAudio;

......
    public override void _Ready()
    {
     ......
        rocketAudio =
            this.GetNode<AudioStreamPlayer3D>("RocketAudio");
    }

    public override void _Process(double delta)
    {
        // 跳跃
        if (Input.IsActionPressed("跳跃"))
        {
            // 播放声音
            if (!rocketAudio.Playing)
            {
                rocketAudio.Play();
       ......
        }
        else
        {
            // 停止播放声音
            if (rocketAudio.Playing)
            {
                rocketAudio.Stop();
            }
        }

  ......
    }
......
}

粒子

准备了一个粒子的素材场景booster_particles.tscn。


image.png

image.png

修改脚本:

using Godot;
using System;

public partial class Player : RigidBody3D
{
    private AudioStreamPlayer explosionAudio;
    private AudioStreamPlayer successAudio;
    private AudioStreamPlayer3D rocketAudio;
    private GpuParticles3D boosterParticles;
    private GpuParticles3D boosterParticlesRight;
    private GpuParticles3D boosterParticlesLeft;

    // 推力 "750.0,3000.0,100.0,or_greater,or_less"
    ///## string(推力值)
    [Export(PropertyHint.Range, "750.0,3000.0")]
    public float THRUST = 1000.0f;

    // 倾斜力
    [Export(PropertyHint.Range, "20.0,1000.0")]
    public float TORQUE_THRUST = 100.0f;

    // 是否过渡中
    private Boolean isTransitioning = false;

    public override void _Ready()
    {
        explosionAudio =
            this.GetNode<AudioStreamPlayer>("ExplosionAudio");
        successAudio =
            this.GetNode<AudioStreamPlayer>("SuccessAudio");
        rocketAudio =
            this.GetNode<AudioStreamPlayer3D>("RocketAudio");
        boosterParticles =
            this.GetNode<GpuParticles3D>("BoosterParticles");
        boosterParticlesRight =
            this.GetNode<GpuParticles3D>("BoosterParticlesRight");
        boosterParticlesLeft =
            this.GetNode<GpuParticles3D>("BoosterParticlesLeft");
    }

    public override void _Process(double delta)
    {
        // 跳跃
        if (Input.IsActionPressed("跳跃"))
        {
            // 开始火箭尾气喷射粒子特效
            boosterParticles.Emitting = true;
            // 播放声音
            if (!rocketAudio.Playing)
            {
                rocketAudio.Play();
            }

            // this.Position = new Vector3(Position.X, Position.Y + (float)delta, Position.Z);
            // 施加中心力,向上
            this.ApplyCentralForce(Basis.Y * (float)delta * THRUST);
        }
        else
        {
            // 停止火箭尾气喷射粒子特效
            boosterParticles.Emitting = false;
            // 停止播放声音
            if (rocketAudio.Playing)
            {
                rocketAudio.Stop();
            }
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("左"))
        {
            
            // 火箭尾气喷射粒子特效
            boosterParticlesLeft.Emitting = true;
            // this.RotateZ((float)delta);
            this.ApplyTorque(Vector3.Back * (float)delta * TORQUE_THRUST);
        }
        else
        {
            // 火箭尾气喷射粒子特效
            boosterParticlesLeft.Emitting = false;
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("右"))
        {
            // 火箭尾气喷射粒子特效
            boosterParticlesRight.Emitting = true;
            // this.RotateZ(-(float)delta);
            this.ApplyTorque(Vector3.Forward * (float)delta * TORQUE_THRUST);
        }
        else
        {
            // 火箭尾气喷射粒子特效
            boosterParticlesRight.Emitting = false;
        }
    }

    public void onBodyEnterd(Node body)
    {
        if (!isTransitioning)
        {
            if (body.GetGroups().Contains("Goal"))
            {
                completeLevel(body.Get("FilePath").AsString());
            }

            if (body.GetGroups().Contains("Hazard"))
            {
                crashSequence();
            }
        }
    }

    public void crashSequence()
    {
        isTransitioning = true;
        GD.Print("大爆炸!");
        this.SetProcess(false);
        explosionAudio.Play();
        Tween tween = this.GetTree().CreateTween();
        ;
        tween.TweenInterval(2.5);
        tween.TweenCallback(Callable.From(() => this.GetTree().ReloadCurrentScene()));
    }

    public void completeLevel(String nextLevelFile)
    {
        isTransitioning = true;
        GD.Print(nextLevelFile + ",挑战成功!");
        successAudio.Play();
        Tween tween = this.GetTree().CreateTween();
        tween.TweenInterval(1.5);
        tween.TweenCallback(Callable.From(() => this.GetTree().ChangeSceneToFile(nextLevelFile)));
    }
}

失败和成功粒子同上。


image.png

脚本如下:

using Godot;
using System;

public partial class Player : RigidBody3D
{
    private AudioStreamPlayer explosionAudio;
    private AudioStreamPlayer successAudio;
    private AudioStreamPlayer3D rocketAudio;
    private GpuParticles3D boosterParticles;
    private GpuParticles3D boosterParticlesRight;
    private GpuParticles3D boosterParticlesLeft;
    private GpuParticles3D explosionParticles;
    private GpuParticles3D successParticles;

    // 推力 "750.0,3000.0,100.0,or_greater,or_less"
    ///## string(推力值)
    [Export(PropertyHint.Range, "750.0,3000.0")]
    public float THRUST = 1000.0f;

    // 倾斜力
    [Export(PropertyHint.Range, "20.0,1000.0")]
    public float TORQUE_THRUST = 100.0f;

    // 是否过渡中
    private Boolean isTransitioning = false;

    public override void _Ready()
    {
        explosionAudio =
            this.GetNode<AudioStreamPlayer>("ExplosionAudio");
        successAudio =
            this.GetNode<AudioStreamPlayer>("SuccessAudio");
        rocketAudio =
            this.GetNode<AudioStreamPlayer3D>("RocketAudio");
        boosterParticles =
            this.GetNode<GpuParticles3D>("BoosterParticles");
        boosterParticlesRight =
            this.GetNode<GpuParticles3D>("BoosterParticlesRight");
        boosterParticlesLeft =
            this.GetNode<GpuParticles3D>("BoosterParticlesLeft");
        explosionParticles =
            this.GetNode<GpuParticles3D>("ExplosionParticles");
        successParticles =
            this.GetNode<GpuParticles3D>("SuccessParticles");
    }

    public override void _Process(double delta)
    {
        // 跳跃
        if (Input.IsActionPressed("跳跃"))
        {
            // 开始火箭尾气喷射粒子特效
            boosterParticles.Emitting = true;
            // 播放声音
            if (!rocketAudio.Playing)
            {
                rocketAudio.Play();
            }

            // this.Position = new Vector3(Position.X, Position.Y + (float)delta, Position.Z);
            // 施加中心力,向上
            this.ApplyCentralForce(Basis.Y * (float)delta * THRUST);
        }
        else
        {
            // 停止火箭尾气喷射粒子特效
            boosterParticles.Emitting = false;
            // 停止播放声音
            if (rocketAudio.Playing)
            {
                rocketAudio.Stop();
            }
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("左"))
        {
            
            // 火箭尾气喷射粒子特效
            boosterParticlesLeft.Emitting = true;
            // this.RotateZ((float)delta);
            this.ApplyTorque(Vector3.Back * (float)delta * TORQUE_THRUST);
        }
        else
        {
            // 火箭尾气喷射粒子特效
            boosterParticlesLeft.Emitting = false;
        }

        // 逆时针旋转形状
        if (Input.IsActionPressed("右"))
        {
            // 火箭尾气喷射粒子特效
            boosterParticlesRight.Emitting = true;
            // this.RotateZ(-(float)delta);
            this.ApplyTorque(Vector3.Forward * (float)delta * TORQUE_THRUST);
        }
        else
        {
            // 火箭尾气喷射粒子特效
            boosterParticlesRight.Emitting = false;
        }
    }

    public void onBodyEnterd(Node body)
    {
        if (!isTransitioning)
        {
            if (body.GetGroups().Contains("Goal"))
            {
                completeLevel(body.Get("FilePath").AsString());
            }

            if (body.GetGroups().Contains("Hazard"))
            {
                crashSequence();
            }
        }
    }

    public void crashSequence()
    {
        isTransitioning = true;
        explosionParticles.Emitting = true;
        GD.Print("大爆炸!");
        this.SetProcess(false);
        explosionAudio.Play();
        Tween tween = this.GetTree().CreateTween();
        ;
        tween.TweenInterval(2.5);
        tween.TweenCallback(Callable.From(() => this.GetTree().ReloadCurrentScene()));
    }

    public void completeLevel(String nextLevelFile)
    {
        isTransitioning = true;
        successParticles.Emitting = true;
        GD.Print(nextLevelFile + ",挑战成功!");
        successAudio.Play();
        Tween tween = this.GetTree().CreateTween();
        tween.TweenInterval(1.5);
        tween.TweenCallback(Callable.From(() => this.GetTree().ChangeSceneToFile(nextLevelFile)));
    }
}

来吧火箭

最终成品。


image.png

都是用MeshInstance3D组件实现。


image.png

Body 大?。?.2,0.2,0.5),位置(0,-0.25,0)
Cockpit 大小(0,0.2,0.5),位置(0,1,0)
Midriff 大?。?.25,0.25,0.25),位置(0,0.25,0)
ThrusterMain 圆柱体, 大小(0.25,0.25,0.4),位置(0,-0.55,0)
ArmLeft 大?。?.2,0.4,0.2),位置(-0.25,-0.19,0)旋转(0 ,0,-44.5)
ThrusterAuxiliary 圆柱体,大?。?.15,0.15,0.4),位置(0.07,-0.3,0)旋转(0 ,0,-44)
ArmRight 大?。?.2,0.4,0.2),位置(0.25,-0.19,0)旋转(0 ,180,-44.5)
ThrusterAuxiliary 圆柱体,大?。?.15,0.15,0.4),位置(0.07,-0.3,0)旋转(0 ,0,-44)
BaseRing 圆环,大?。?.4,0.5),位置(0,-0.55,0)

颜色

将颜色改为#cc3820

image.png

另存为材质。


image.png

可以直接往模型上拖,或者选择快速加载。


image.png

image.png
image.png

背景

复制地板的组件,创建各种大小不一摆放不一的柱体即可。


image.png
image.png
image.png

然后做成地板的子节点,保存为场景Background.tscn


image.png

照明

一开始我们有默认的环境3要素,其中包括太阳光,但这个游戏上下围起来的话,需要做一个全光照明。
将环境和太阳光剪切到Backgroud.tscn场景,其他关卡都删掉。
调整参数。

环境调成自定义颜色,颜色#120b07

image.png

调整太阳光的角度和强度。


image.png

增加三个全域照明,就是蜡烛、白炽灯效果的光照OmniLight3D。


image.png

调整XY轴位置,光的Range和Attenuation值(强度和范围)。


image.png

游戏效果就出来了:
image.png

游戏导出

游戏导出需要导出模板,godot软件的下载国内是失败的,科学上网也没啥用。、
办法:https://github.com/godotengine/godot/releases/tag

image.png

下载完成后,再本地导入即可。

image.png
image.png

然后从文件导入,修改名字,勾上内嵌PCK(会生成exe文件),最后点击导出项目。

image.png

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容