반응형

파이게임과 GUI 라이브러리를 사용해 보자.

ImGui를 사용해 보려 했는데, OpenGL을 이용해야 하고 pygame.Surface.fill()을 사용할 수 없는 등 마음에 들지 않아 Pygame GUI를 사용하기로 했다.

 

pygame-gui를 설치한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import pygame
import pygame_gui
 
pygame.init()
pygame.display.set_caption("Super fun game development")
screenSize = (640480)
screen = pygame.display.set_mode(screenSize, pygame.DOUBLEBUF | pygame.RESIZABLE)
clock = pygame.time.Clock()
 
manager = pygame_gui.UIManager(screenSize)
hello_button = pygame_gui.elements.UIButton(relative_rect=pygame.Rect((1010), (10050)),
                                            text='Say Hello', manager=manager)
 
running = True
 
while running:
    time_delta = clock.tick(60)/1000
    # As you may have noticed we also had to create a pygame Clock to track the amount of time
    # in seconds that passes between each loop of the program. We need this 'time_delta' value
    # because several of the UI elements make use of timers and this is a convenient place to
    # get it.
        
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            running = False
        
        if event.type == pygame_gui.UI_BUTTON_PRESSED:
              if event.ui_element == hello_button:
                  print('Hello World!')
        
        manager.process_events(event)
    
    manager.update(time_delta)
 
    screen.fill("black")
    pygame.draw.circle(screen, "gray", screen.get_rect().center, 100)
 
    manager.draw_ui(screen)
 
    pygame.display.flip()
 
pygame.quit()
 

 

코드를 입력하고 실행한다.

 

버튼이 표시된다.

 

2024.01.28 - [Python] - [Pygame] Box2D 파이게임 물리 라이브러리

위 링크의 코드를 이용해 조금 더 실용적인 예제를 만들어 보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import math
import pygame
import pygame_gui
from Box2D import *
 
pygame.init()
pygame.display.set_caption("Physics Test")
screen = pygame.display.set_mode((640480))
running = True
player = pygame.image.load("player.png").convert()
 
manager = pygame_gui.UIManager((640480))
again_button = pygame_gui.elements.UIButton(relative_rect=pygame.Rect((35010), (200100)),
                                            text='Play again', manager=manager)
 
world = b2World(gravity=(09.8), doSleep=True)
 
groundBody = world.CreateStaticBody(position=(0400), shapes=b2PolygonShape(box=(5000)))
 
wallBody = world.CreateStaticBody(position=(3000), shapes=b2PolygonShape(box=(0400)))
 
playerBody = world.CreateDynamicBody(position=(00), linearVelocity=(500), angularVelocity=0.2)
playerFixtureDef = playerBody.CreatePolygonFixture(box=(player.get_width()/2,
                              player.get_height()/2), density=1, friction=0.5, restitution=0.7)
 
timeStep = 1.0 / 300
vel_iters, pos_iters = 62
  
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
            playerBody.transform = ((00), 0)
            playerBody.linearVelocity = (500)
            playerBody.angularVelocity = 0.2
        elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            running = False
            
        if event.type == pygame_gui.UI_BUTTON_PRESSED:
            if event.ui_element == again_button:
                playerBody.transform = ((00), 0)
                playerBody.linearVelocity = (500)
                playerBody.angularVelocity = 0.2
        
        manager.process_events(event)
            
    manager.update(timeStep)
    
    world.Step(timeStep, vel_iters, pos_iters)
    world.ClearForces()
     
    screen.fill("black")
    pygame.draw.rect(screen, "brown", (040060020))
    pygame.draw.rect(screen, "yellow", (300020400))
 
    rotated_player = pygame.transform.rotate(player, playerBody.angle * 180/math.pi)
    
    screen.blit(rotated_player, (playerBody.position[0- rotated_player.get_width()/2,
                                 playerBody.position[1- rotated_player.get_height()/2))
 
    manager.draw_ui(screen)
    
    pygame.display.flip()
    
pygame.quit()
 

 

코드를 입력하고 실행한다.

 

버튼을 클릭하면 캐릭터가 다시 던져진다.

 

※ 참고

GUIs with pygame

Pygame GUI

 

반응형
Posted by J-sean
:
반응형

베지어 스플라인 곡선을 그려보자.

 

Node2D를 생성하고 스크립트를 추가한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using Godot;
 
public partial class test : Node2D
{    
    private Curve2D pos;
    
    private Vector2 position0;
    private Vector2 position1;
    private Vector2 position2;
    private Vector2 position3;
    private Vector2 position4;
 
    private Vector2 control0;
    private Vector2 control1;
 
    public override void _Ready()
    {
        pos = new Curve2D();
 
        position0 = new Vector2(200200);
        position1 = new Vector2(400400);
        position2 = new Vector2(600200);
        position3 = new Vector2(800400);
        position4 = new Vector2(1000200);
 
        control0 = new Vector2(-1000);
        control1 = new Vector2(1000);
 
        BezierSpline(position0, position1, position2, position3,
            position4, control0, control1);
    }
 
    public override void _Draw()
    {
        //DrawPolyline(pos.GetBakedPoints(), Colors.Red, 5);
        DrawPolyline(pos.Tessellate(), Colors.Red, 5);
    }
 
    private void BezierSpline(Vector2 p0, Vector2 p1, Vector2 p2,
        Vector2 p3, Vector2 p4, Vector2 c0, Vector2 c1)
    {
        pos.AddPoint(p0, c0, c1);
        pos.AddPoint(p1, c0, c1);
        pos.AddPoint(p2, c0, c1);
        pos.AddPoint(p3, c0, c1);
        pos.AddPoint(p4, c0, c1);
    }
}
 

 

 

위와 같이 코드를 작성하고 실행한다.

_Draw()는 처음 한 번만 호출된다. 스플라인이 변경되거나 _Draw()를 다시 호출할 필요가 있다면 QueueRedraw()를 사용한다.

 

각 포인트에서 컨트롤이 적용된 스플라인이 표시된다.

 

※ 참고

Custom drawing in 2D

Beziers, curves, and paths

About Spline Interpolation

 

반응형
Posted by J-sean
:

[Godot] Wall Jump 벽 점프

Godot 2023. 10. 2. 16:53 |
반응형

벽을 타고 점프해 보자.

 

바닥, 벽, 캐릭터를 적당히 배치한다. 캐릭터에는 스크립트를 추가하자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using Godot;
 
public partial class CharacterBody2D : Godot.CharacterBody2D
{
    public const float Speed = 300.0f;
    public const float JumpVelocity = -400.0f;
 
    public float gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();
 
    public override void _PhysicsProcess(double delta)
    {
        Vector2 velocity = Velocity;
 
        if (!IsOnFloor())
            velocity.Y += gravity * (float)delta;
        
        if (Input.IsActionJustPressed("ui_accept"&& IsOnFloor())
            velocity.Y = JumpVelocity;
 
        if (Input.IsActionJustPressed("ui_accept"&& IsOnWallOnly())
            velocity.Y = JumpVelocity;
 
        Vector2 direction = Input.GetVector("ui_left""ui_right""ui_up""ui_down");
        if (direction != Vector2.Zero)
        {
            velocity.X = direction.X * Speed;
        }
        else
        {
            velocity.X = Mathf.MoveToward(Velocity.X, 0, Speed);
        }
 
        Velocity = velocity;
        MoveAndSlide();
    }
}
 

 

 

IsOnWallOnly() 함수를 사용한 스크립트를 작성한다. 

 

벽을 타고 wall jump가 가능하다.

 

※ 참고

CharacterBody2D

Using CharacterBody2D/3D

 

반응형
Posted by J-sean
:
반응형

Instantiate로 생성한 씬을 반복 재사용해 보자.

 

스프라이트 하나를 생성하고 씬으로 저장(Player.tscn)한다.

 

res:// 에 스크립트를 생성하고 Autoload에 추가한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using Godot;
 
public partial class Control : Node
{
    public PackedScene Scene;
    public Timer timer;
    public Sprite2D Sprite;
 
    public override void _Ready()
    {
        // C# has no preload, so you have to always use ResourceLoader.Load<PackedScene>().
        Scene = ResourceLoader.Load<PackedScene>("res://Player.tscn");
        Sprite = Scene.Instantiate<Sprite2D>();
 
        timer = new Timer();
        timer.Connect("timeout", Callable.From(OnTimeOut));
        timer.WaitTime = 1.0;
        AddChild(timer);
        timer.Start();        
    }
    
    public override void _Process(double delta)
    {        
    }
 
    public void OnTimeOut()
    {
 
        Sprite.Position = new Vector2(GD.RandRange(0, (int)GetViewport().GetVisibleRect().Size.X),
            GD.RandRange(0, (int)GetViewport().GetVisibleRect().Size.Y));
        if (Sprite.GetParent() == null )
        {
            AddChild(Sprite);
            // Adds a child node. Nodes can have any number of children, but every child must have
            // a unique name. Child nodes are automatically deleted when the parent node is deleted,
            // so an entire scene can be removed by deleting its topmost node.
        }
        else
        {
            RemoveChild(Sprite);
            // Removes a child node. The node is NOT deleted and must be deleted manually.
        }
    }
}
 

 

 

RemoveChild()로 제거한 자식 노드는 삭제되지는 않는다. AddChild()로 다시 추가 할 수 있다.

 

게임을 실행하면 씬에 스프라이트가 반복해서 나타났다 사라진다.

 

게임이 실행된 상태에서 Remote 탭을 확인하면 GlobalControl의 자식 노드로 Sprite2D가 반복적으로 추가됐다 삭제 된다.

 

반응형
Posted by J-sean
:
반응형

코드로 캐릭터를 생성해 보자.

 

스프라이트를 준비하고 스크립트(Player.cs)를 추가한다.

 

타이머를 자식노드로 추가한다. Autostart를 체크하고 Wait Time은 5초로 설정한다.

 

timeout 시그널을 스프라이트에 연결한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using Godot;
 
public partial class Player : Sprite2D
{
    // Called when the node enters the scene tree for the first time.
    public override void _Ready()
    {
    }
 
    // Called every frame. 'delta' is the elapsed time since the previous frame.
    public override void _Process(double delta)
    {
    }
 
    public void OnTimerTimeout()
    {
        QueueFree();
    }
}
 

 

 

타임아웃 시그널이 발생하면 삭제 큐에 노드를 추가하는(QueuFree) Player.cs 스크립트를 작성한다.

 

 

res:// 에 스크립트(Controls.cs)를 추가한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using Godot;
 
public partial class Control : Node
{
    public PackedScene Scene;
    public Timer timer;
 
    public override void _Ready()
    {
        // C# has no preload, so you have to always use ResourceLoader.Load<PackedScene>().
        Scene = ResourceLoader.Load<PackedScene>("res://Player.tscn");
        
        timer = new Timer();
        timer.Connect("timeout", Callable.From(OnTimeOut));
        timer.WaitTime = 1.0;
        AddChild(timer);
        timer.Start();
    }
    
    public override void _Process(double delta)
    {        
    }
 
    public void OnTimeOut()
    {
        Sprite2D Sprite = Scene.Instantiate<Sprite2D>();
        Sprite.Position = new Vector2(GD.RandRange(0, (int)GetViewport().GetVisibleRect().Size.X),
            GD.RandRange(0, (int)GetViewport().GetVisibleRect().Size.Y));
        AddChild(Sprite);
    }
}
 

 

 

Control.cs를 작성한다. _Ready()에서 타이머를 코드로 생성하고 타임아웃 시그널이 발생하면 씬을 생성한다.

 

Project Settings - Autoload 에 Controls.cs를 추가한다.

 

게임을 실행하면 캐릭터가 생성되고 잠시 후 사라진다.

※ 참고

Nodes and scenes

Callable

 

반응형
Posted by J-sean
:
반응형

게임 전체에서 간단히 사용할 수 있는 전역 클래스 멤버 변수를 만들어 보자.

 

전역 클래스 멤버 변수를 저장할 스크립트(Global.cs)를 생성한다. Template: Object: Empty

 

1
2
3
4
5
6
using Godot;
 
public partial class Global : Node
{
    public static int a = 10;
}
 

 

 

어떤 씬에도 소속되지 않은 스크립트(Global.cs)가 생성 된다.

 

노드를 하나 생성하고 전역 변수를 사용할 스크립트를 추가한다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using Godot;
 
public partial class Script : Sprite2D
{
    // Called when the node enters the scene tree for the first time.
    public override void _Ready()
    {
        GD.Print(Global.a);        
    }
 
    // Called every frame. 'delta' is the elapsed time since the previous frame.
    public override void _Process(double delta)
    {
    }
}
 

 

 

Project Settings - Autoload 에 Global.cs 스크립트를 추가한다.

Autoload에 추가되는 스크립트나 씬은 게임이 시작되면 자동으로 로드된다.

 

게임을 실행하면 Output 창에 Global.a 값이 출력된다.

 

게임이 실행 중인 상태에서 Remote 탭을 확인하면 Global 스크립트가 root 노드 아래 로드되어 있다.

 

※ 참고

Singletons (Autoload)

 

반응형
Posted by J-sean
:
반응형

다른 스크립트에 선언된 변수에 접근해 보자.

 

첫 번째 캐릭터를 생성하고 스크립트를 추가한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using Godot;
 
public partial class Character1 : Sprite2D
{
    public int a = 10;
 
    // Called when the node enters the scene tree for the first time.
    public override void _Ready()
    {
    }
 
    // Called every frame. 'delta' is the elapsed time since the previous frame.
    public override void _Process(double delta)
    {
    }
}
 

 

 

두 번째 캐릭터를 생성하고 스크립트를 추가한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using Godot;
 
public partial class Character2 : Sprite2D
{
    // Called when the node enters the scene tree for the first time.
    public override void _Ready()
    {
        GD.Print(GetNode<Character1>("../../Character_1/Sprite2D").a);
    }
 
    // Called every frame. 'delta' is the elapsed time since the previous frame.
    public override void _Process(double delta)
    {
    }
}
 

 

 

두 캐릭터를 자식 노드로 갖는 씬을 생성한다. 두 캐릭터는 부모 자식 관계가 아닌 형제 관계로 설정한다.

 

게임을 실행하면 Output 창에 Character1의 멤버 변수 a가 출력된다.

 

반응형
Posted by J-sean
:
반응형

2D 환경에서 엠비언트 라이트를 조절해 보자.

 

2D 환경을 준비하고 스프라이트를 배치한다.

 

CanvasModulate 노드를 추가한다.

 

Inspector - Color 속성을 클릭하고 원하는 색상을 선택한다.

 

CanvasModulate 노드는 지정한 색상으로 씬을 어둡게 한다.

 

이번엔 DirectionalLight2D 노드를 추가한다. 기본적으로 태양처럼 동작하기 때문에 스프라이트에 흰색이 추가돼 밝아진다.

 

반대로 동작시키기 위해 Blend Mode 속성을 Subtract로 바꾸고 Color 속성을 녹색으로 바꾸면 스프라이트에서 녹색이 제외된다.

 

※ 참고

2D lights and shadows

 

반응형
Posted by J-sean
: