27、实战物理益智游戏

第27章 实战:物理益智游戏

物理益智游戏要素示意图

复制代码
弹球发射:

    🧙 按住拖拽 → 松开发射
    ↗
   ●──────→ 弹球飞行方向(反向)

弹力球弹跳:

   █         █
   █  ●↗    █
   █    ↘   █    碰到墙壁反弹
   █      ↘ █    bounce = 0.9(高弹性)
   █────────█

关节约束:

   PinJoint2D(铰链)      DampedSpringJoint2D(弹簧)
      ●                       ●
      │                       │
      ◉ ← 旋转点              ∿∿∿ ← 弹簧连接
      │                       │
      ●                       ●

物体切割:

   切割前          切割后
   ┌────┐        ┌──┐ ┌──┐
   │    │   →→   │  │ │  │
   │    │        └──┘ └──┘
   └────┘        上半  下半

消除类玩法:

   ┌─┬─┬─┬─┬─┬─┐
   │🔴│🔵│🟢│🟡│🔴│🔵│  ← 3个相同颜色连线
   ├─┼─┼─┼─┼─┼─┤        → 消除
   │🟢│🔴│🔵│🟢│🟡│🔴│  ← 上方方块下落填补
   ├─┼─┼─┼─┼─┼─┤
   │🟡│🟢│🟡│🔴│🔵│🟢│
   └─┴─┴─┴─┴─┴─┘

27.1 RigidBody2D 深入

gdscript 复制代码
extends RigidBody2D

# 常用属性
mass = 1.0                    # 质量
gravity_scale = 1.0           # 重力倍率
linear_damp = 0.0             # 线性阻尼(空气阻力)
angular_damp = 0.0            # 角度阻尼

# 物理模式
freeze = false                # 冻结(不参与物理)
freeze_mode = RigidBody2D.FREEZE_MODE_KINEMATIC  # 冻结时作为运动学体

# 应用力
apply_central_force(Vector2(100, 0))       # 持续力(受质量影响)
apply_central_impulse(Vector2(500, -300))  # 冲量(一次性)
apply_force(Vector2(100, 0), Vector2(0, -20))  # 指定位置的力(产生旋转)

27.2 物理材质

gdscript 复制代码
# 创建弹力球
func create_bouncy_ball() -> RigidBody2D:
    var ball := RigidBody2D.new()
    var shape := CollisionShape2D.new()
    var circle := CircleShape2D.new()
    circle.radius = 15.0
    shape.shape = circle
    ball.add_child(shape)
    
    var mat := PhysicsMaterial.new()
    mat.bounce = 0.9          # 高弹性
    mat.friction = 0.1        # 低摩擦
    ball.physics_material_override = mat
    
    return ball

# 创建冰块(低摩擦)
func create_ice_block() -> RigidBody2D:
    var block := RigidBody2D.new()
    var mat := PhysicsMaterial.new()
    mat.bounce = 0.1
    mat.friction = 0.02       # 极低摩擦
    block.physics_material_override = mat
    return block

# 创建橡胶球(高弹性)
func create_rubber_ball() -> RigidBody2D:
    var ball := RigidBody2D.new()
    var mat := PhysicsMaterial.new()
    mat.bounce = 1.0          # 完美弹跳
    mat.friction = 0.8
    ball.physics_material_override = mat
    return ball

27.3 关节约束

gdscript 复制代码
# PinJoint2D:铰链(旋转连接)
func create_hinge(body_a: Node2D, body_b: Node2D, pivot: Vector2) -> void:
    var joint := PinJoint2D.new()
    joint.node_a = body_a.get_path()
    joint.node_b = body_b.get_path()
    joint.global_position = pivot
    add_child(joint)

# SpringJoint2D:弹簧连接
func create_spring(body_a: Node2D, body_b: Node2D, length: float, stiffness: float) -> void:
    var joint := DampedSpringJoint2D.new()
    joint.node_a = body_a.get_path()
    joint.node_b = body_b.get_path()
    joint.length = length
    joint.stiffness = stiffness
    joint.damping = 0.5
    add_child(joint)

# GrooveJoint2D:滑槽约束
func create_groove(body_a: Node2D, body_b: Node2D, groove_start: Vector2, groove_end: Vector2) -> void:
    var joint := GrooveJoint2D.new()
    joint.node_a = body_a.get_path()
    joint.node_b = body_b.get_path()
    joint.global_position = groove_start
    joint.groove_length = groove_start.distance_to(groove_end)
    add_child(joint)

27.4 物理交互玩法

弹球发射

gdscript 复制代码
extends Node2D

@export var ball_scene: PackedScene
@export var launch_force: float = 800.0

var is_aiming: bool = false
var aim_start: Vector2 = Vector2.ZERO

func _unhandled_input(event: InputEvent) -> void:
    if event is InputEventMouseButton:
        if event.button_index == MOUSE_BUTTON_LEFT:
            if event.pressed:
                is_aiming = true
                aim_start = get_global_mouse_position()
            else:
                launch_ball(aim_start, get_global_mouse_position())
                is_aiming = false

func launch_ball(from: Vector2, to: Vector2) -> void:
    var direction := (from - to).normalized()  # 反向发射
    var ball: RigidBody2D = ball_scene.instantiate()
    ball.global_position = from
    ball.apply_central_impulse(direction * launch_force)
    add_child(ball)

func _draw() -> void:
    if is_aiming:
        var mouse_pos := to_local(get_global_mouse_position())
        var aim_dir := (aim_start - get_global_mouse_position()).normalized()
        var from := to_local(aim_start)
        draw_line(from, from + aim_dir * 100, Color.RED, 2.0)

物体切割

gdscript 复制代码
# 使用 Geometry2D 实现简单切割
func cut_polygon(polygon: PackedVector2Array, cut_start: Vector2, cut_end: Vector2) -> Array:
    # 获取切割线与多边形的交点
    var intersection := Geometry2D.segment_intersects_segment(
        cut_start, cut_end, polygon[0], polygon[polygon.size() - 1]
    )
    
    if intersection == null:
        return [polygon]  # 没有交点,不切割
    
    # 分割多边形(简化实现)
    var upper_half: PackedVector2Array = []
    var lower_half: PackedVector2Array = []
    
    for point in polygon:
        # 根据点在切割线的哪一侧分组
        var cross := (cut_end - cut_start).cross(point - cut_start)
        if cross > 0:
            upper_half.append(point)
        else:
            lower_half.append(point)
    
    return [upper_half, lower_half]

27.5 消除类玩法

gdscript 复制代码
extends Node2D

@export var block_scene: PackedScene
@export var grid_size: Vector2i = Vector2i(8, 10)
@export var cell_size: float = 40.0

var grid: Array = []  # 二维数组

func _ready() -> void:
    init_grid()
    fill_grid()

func init_grid() -> void:
    grid.clear()
    for x in grid_size.x:
        grid.append([])
        for y in grid_size.y:
            grid[x].append(null)

func fill_grid() -> void:
    for x in grid_size.x:
        for y in grid_size.y:
            spawn_block(x, y)

func spawn_block(x: int, y: int) -> void:
    var block: RigidBody2D = block_scene.instantiate()
    block.global_position = grid_to_world(x, y)
    block.block_type = randi() % 4  # 4种颜色
    add_child(block)
    grid[x][y] = block

func grid_to_world(x: int, y: int) -> Vector2:
    return Vector2(x * cell_size, y * cell_size) + Vector2(cell_size / 2, cell_size / 2)

func world_to_grid(pos: Vector2) -> Vector2i:
    return Vector2i(int(pos.x / cell_size), int(pos.y / cell_size))

func check_matches() -> void:
    # 检查3个或更多相同颜色的连线
    var matches: Array[Vector2i] = []
    
    # 水平检查
    for y in grid_size.y:
        var count := 1
        for x in range(1, grid_size.x):
            if grid[x][y] and grid[x-1][y] and grid[x][y].block_type == grid[x-1][y].block_type:
                count += 1
            else:
                if count >= 3:
                    for i in count:
                        matches.append(Vector2i(x - 1 - i, y))
                count = 1
    
    # 消除匹配的方块
    for pos in matches:
        if grid[pos.x][pos.y]:
            grid[pos.x][pos.y].queue_free()
            grid[pos.x][pos.y] = null
    
    # 下落填补空缺
    apply_gravity()

func apply_gravity() -> void:
    for x in grid_size.x:
        var write_y := grid_size.y - 1
        for read_y in range(grid_size.y - 1, -1, -1):
            if grid[x][read_y] != null:
                if read_y != write_y:
                    grid[x][write_y] = grid[x][read_y]
                    grid[x][read_y] = null
                    # 动画移动到新位置
                    var tween := create_tween()
                    tween.tween_property(grid[x][write_y], "global_position", grid_to_world(x, write_y), 0.2)
                write_y -= 1

实操练习

练习:简单弹球物理场景

gdscript 复制代码
# 创建场景包含:
# 1. 一个可以拖拽发射的球
# 2. 几个不同材质的障碍物(弹力墙、冰面、粘性表面)
# 3. 一个目标区域(检测球进入)
# 4. 使用关节约束创建可摆动的障碍
# 5. 记录尝试次数和最佳成绩
相关推荐
一孤程11 小时前
游戏测试专题第四篇:游戏性能测试实战-帧率/内存/发热全覆盖
游戏·测试
小小数媒成员16 小时前
如何优化代码适应托管内存?
游戏·unity·游戏引擎
humors22116 小时前
支付宝游戏灵画师简易步骤整理
游戏·技巧·手游·攻略·灵画师
河南花仙子科技16 小时前
企业定制小游戏助力品牌软性传播
大数据·科技·游戏·小程序
liulilittle20 小时前
为什么采用全局管理缓存及状态:麻将客户端状态管理
服务器·网络·游戏·客户端·异步·mahjong·麻将
狂人开飞机1 天前
21、游戏架构设计模式
游戏·游戏引擎·godot
烛阴1 天前
Godot 4 入门(04):CharacterBody2D 实现八方向移动
godot
bj_bluewei_tech2 天前
拯救者游戏本维修卡 logo 无限重启,确认显卡虚焊故障
游戏·电脑
Jae den3 天前
网络延迟为什么会影响游戏和实时业务?
网络·游戏